casting to void* to pass objects to pthread in c++

2019-02-23 09:31发布

问题:

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.

回答1:

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.



回答2:

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.