I'm a little confused about how to pass an object to the pthread_create function. I've found a lot of piecemeal information concerning casting to void*, passing arguments to pthread_create, etc., but nothing that ties it all together. I just want to make sure I've tied it all together and am not doing anything stupid. Let's say I have the following thread class:
Edit: fixed mis-matched static_cast
.
class ProducerThread {
pthread_t thread;
pthread_attr_t thread_attr;
ProducerThread(const ProducerThread& x);
ProducerThread& operator= (const ProducerThread& x);
virtual void *thread_routine(void *arg) {
ProtectedBuffer<int> *buffer = static_cast<ProtectedBuffer<int> *> arg;
int randomdata;
while(1) {
randomdata = RandomDataGen();
buffer->push_back(randomdata);
}
pthread_exit();
}
public:
ProtectedBuffer<int> buffer;
ProducerThread() {
int err_chk;
pthread_attr_init(&thread_attr);
pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);
err_chk = pthread_create(&thread, &thread_attr, thread_routine, static_cast<void *> arg);
if (err_chk != 0) {
throw ThreadException(err_chk);
}
}
~ProducerThread() {
pthread_cancel(&thread);
pthread_attr_destroy(&thread_attr);
}
}
To clarify, the data in the ProtectedBuffer
class can only be accessed with methods like ProtectedBuffer::push_back(int arg)
, which use mutexes to protect the actual data.
My main question is: am I using static_cast
correctly? And my secondary question is do I need that first line in virtual void *thread_routine(void *arg)
where I copy the passed void pointer to a pointer to ProtectedBuffer
?
Also, if I've done anything else that might cause problems, I'd appreciate hearing it.
If you want to go this route, I believe you want something like this:
Edit: Based on James Kanze's answer, add a separate activate
method to launch the thread after construction is finished.
class GenericThread {
protected:
GenericThread () {
//...
}
virtual ~GenericThread () {}
int activate () {
return pthread_create(..., GenericThreadEntry, this);
}
virtual void * thread_routine () = 0;
#if 0
// This code is wrong, because the C routine callback will do so using the
// C ABI, but there is no guarantee that the C++ ABI for static class methods
// is the same as the C ABI.
static void * thread_entry (void *arg) {
GenericThread *t = static_cast<GenericThread *>(arg);
return t->thread_routine();
}
#endif
};
extern "C" void * GenericThreadEntry (void *) {
GenericThread *t = static_cast<GenericThread *>(arg);
return t->thread_routine();
}
Then, ProducerThread
would derive from GenericThread
.
Edit: Searching for extern "C"
in the C++ Standard. revealed no requirement that a function pointer must point to a function with C linkage to be callable by a C library routine. Since pointers are being passed, linkage requirements do not apply, as linkage is used to resolve names. A pointer to a static method is a function pointer, according to C++ 2011 draft (n3242), Sec. 3.9.2p3:
Except for pointers to static members, text referring to pointers does not apply to pointers to members.
Edit: Mea culpa. The C library will invoke the callback function assuming the C application binary interface. A function with C++ linkage may use a different ABI than the C ABI. This is why it is required to use a function with extern "C"
linkage when passing to a callback function to a C library. My sincere apologies to James Kanze for doubting him, and my sincere thanks to Loki Astari for setting me straignt.
There are a number of problems with your code. For starters, I don't
see where the arg
you are casting is declared, so I can't say whether
the case is appropriate.
Perhaps more importantly, thread_routine
is a member function, so it
can't be converted to a pointer to a function. The function passed to
pthread_create
must be extern "C"
, so it cannot be a member, period;
it must be a free function declare extern "C"
. If you want to call a
member function, pass a pointer to the object as the last argument, and
dereference it in the extern "C"
function:
extern "C" void* startProducerThread( void* arg )
{
return static_cast<ProducerThread*>( arg )->thread_routine();
}
And to start the thread:
int status = pthread_create( &thread, &thread_attr, startProducerThread, this );
Just don't do this in a constructor. The other thread might start
running before the object is fully constructed, with disasterous
effects.
Also, be very sure that the cast in startProducerThread
is to
exactly the same type as the pointer passed into pthread_create
. If
you cast to a base class in startProducerThread
, then be very, very
sure that it is a pointer to that base class that you pass to
pthread_create
; use an explicit cast if necessary (to the type in
startProducerThread
, not to void*
).
Finally, while not relevant to your actual question: if
ProtectedBuffer
has an interface like that of std::vector
, and
returns references to internal data, there's no way you can make it
thread safe. The protection needs to be external to the class.