Reputation: 663
I have a library with APIs using std::shared_ptr
as arguments.
I would like to use these APIs together with pthreads.
What I'm doing is: getting a raw pointer from the shared_ptr, in order to pass it to the pthread.
Create a new shared_ptr from the raw one and call my API from another thread.
However I get a double free or corruption
error when converting the raw pointer back to a shared one.
This is my code
#include <memory>
#include <iostream>
#include <thread>
#include <pthread.h>
void* print_task(void* ptr)
{
int* val_raw = static_cast<int*>(ptr);
std::shared_ptr<int> val(val_raw);
// CALL MY API WHICH TAKES A SHARED_PTR AS ARGUMENT
std::cout<<"thread job done \n";
}
int main(int argc, char ** argv)
{
pthread_t thread;
std::shared_ptr<int> val = std::make_shared<int>(10);
pthread_create(&thread, nullptr, &print_task, static_cast<void *>(val.get()));
std::this_thread::sleep_for(std::chrono::seconds(5));
return 0;
}
I guess that I'm doing something wrong with all the conversions from shared to raw pointer, because the same code using std::threads (where I can pass directly a shared_ptr) works. However I need to set thread priorities, thus I'm trying to do this with pthreads.
Do you know how to change my code in order to be able to pass a shared pointer and use it inside a pthread?
Upvotes: 2
Views: 2197
Reputation: 48655
I think this is a tricky little conundrum to be honest. The problem you are having is that by passing the raw pointer to your thread function you end up with two independant shared pointers managing the same object instead of two connected shared pointers sharing ownership.
Consequently both shared pointers try to delete it.
You also have a nasty lifetime problem to avoid if you pass a pointer to the shared pointer itself because you can not guarantee the shared pointer will not go out of scope before the new thread has copied it.
I get round that by passing a second dynamically allocated std::shared_ptr
created from the original std::shared_ptr
which guarantees the shared pointer's control block will live until the new thread tries to copy it.
#include <memory>
#include <iostream>
#include <thread>
#include <pthread.h>
void* print_task(void* ptr)
{
// obtain the shared pointer from the dynamically created one guarantees
// we will bee accessing a living control block preventing an
// end-of-lifetime catastrophe
std::shared_ptr<int> val = *static_cast<std::shared_ptr<int>*>(ptr);
// DON'T FORGET TO DELETE THIS!!!
// We had to allocate this dynamically to guarantee it lived until after
// it was used.
delete static_cast<std::shared_ptr<int>*>(ptr);
// CALL MY API WHICH TAKES A SHARED_PTR AS ARGUMENT
std::cout << "thread job done \n";
return nullptr;
}
int main()
{
pthread_t thread;
// create the shared resource. This MAY go out of scope
// before the new thread copies it (however unlikely you
// think that is).
std::shared_ptr<int> val = std::make_shared<int>(10);
// So instead of sending the shared pointer we create a NEW std::shared_ptr
// which will keep the shared pointer's control block alive even if the original
// shared pointer goes out of scope.
pthread_create(&thread, nullptr, &print_task,
static_cast<void*>(new std::shared_ptr<int>(val)));
// Do other time consuming thread stuff here.
std::this_thread::sleep_for(std::chrono::seconds(2));
// Tidy up.
void* ret = nullptr;
pthread_join(thread, &ret);
return 0;
}
Of course you now have a new
/delete
pair here but you still gain in passing the shared ownership round between threads.
I suppose you can even get rid of that delete
by adopting the passed in raw pointer by a temporary std::unique_ptr
.
void* print_task(void* ptr)
{
// obtain the shared pointer from the dynamically created one guarantees
// we will bee accessing a living control block preventing an
// end-of-lifetime catastrophe
std::shared_ptr<int> val = *std::unique_ptr<std::shared_ptr<int>>(static_cast<std::shared_ptr<int>*>(ptr));
// CALL MY API WHICH TAKES A SHARED_PTR AS ARGUMENT
std::cout << "thread job done \n";
return nullptr;
}
Upvotes: 1
Reputation: 17444
As mentioned in the comments already, the problem is passing a shared pointer through a raw void pointer, so I'll ignore the threading part for now:
// this is what we have and what we want to pass to the given function
shared_ptr<some_type> sptr;
// function to somehow pass the shared pointer to
void function(void* ptr);
// As always, when passing anything that doesn't fit into
// the raw pointer, we need to do dynamic allocation:
void* arg = new shared_ptr<some_type>(sptr);
// we can now pass this to the function as intended:
function(arg);
// Note that we give up ownership of the dynamically allocated
// shared pointer instance. Hence, the called function must
// release that object again (it takes ownership). The function
// therefore starts like this:
void function(void* ptr)
{
// convert the typeless pointer to a typed pointer again
shared_ptr<some_type>* psptr = static_cast<shared_ptr<some_type>*>(ptr);
// move the content to a new, local instance
shared_ptr<some_type> sptr = *psptr;
// release the dynamically allocated shared pointer again
delete psptr;
/// ... code using sptr here ...
}
Now, while this is guaranteed to work, under some circumstances this may not be an optimal solution:
Upvotes: 2