简体   繁体   中英

Pointer to this* in thread

I have a class and a library (lwip). For some reasons I need to call library's function of thread creation like :

/** The only thread function:
 * Creates a new thread
 * @param name human-readable name for the thread (used for debugging purposes)
 * @param thread thread-function
 * @param arg parameter passed to 'thread'
 * @param stacksize stack size in bytes for the new thread (may be ignored by ports)
 * @param prio priority of the new thread (may be ignored by ports) */
sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread, void *arg, int    
stacksize, int prio);

Inside this function we call pthread:

code = pthread_create(&tmp,NULL,(void *(*)(void *)) function, arg);

My call looks like :

sys_thread_new("main_thread",(lwip_thread_fn)&this->main_thread, NULL,   
               DEFAULT_THREAD_STACKSIZE,DEFAULT_THREAD_PRIO); 

My class method works fine, but I need to change some fielsd of CURRENT class (like 'state' or else) I have an Idea to pass a pointer to current class to that thread and in thread function change class fields. Some kind of:

 sys_thread_new("main_thread",(lwip_thread_fn)&this->main_thread, (void*)this,  
                DEFAULT_THREAD_STACKSIZE, DEFAULT_THREAD_PRIO); 

Then in main_thread:

void lwip::main_thread(void *arg) {
lwip *p = (lwip*)arg;
 p->state = 1;
}

Something like that. But it seems I do something wrong -

     Program received signal SIGSEGV, Segmentation fault.
 [Switching to Thread 0x7ffff6e8e700 (LWP 4985)]
 0x0000000000403a75 in lwip::main_thread (this=0x7fffffffe4f0, arg=0x80) at    
 ../src/lwip.cpp:50
 50         p->state = 1;

There are two problems here: If the main_thread member function is a static member function, you pass a pointer to it using &lwip::main_thread , no casting should be needed. If the function is not static , then you must make it static .

The other problem is that if the instance ( this ) you pass to the thread function is destructed, the thread function now has a pointer to a destructed object. Be careful with temporary object or passing instances by value.


If the actual thread function can't be static , you can easily solve it with a static wrapper function:

class lwip
{
    ...

private:
    void main_thread() { ... }

    static void* main_thread_wrapper(void* arg)
    {
        reinterpret_cast<lwip*>(arg)->main_thread();
        return nullptr;
    }
};

...

sys_thread_new("main_thread", &lwip::main_thread_wrapper, this,
           DEFAULT_THREAD_STACKSIZE,DEFAULT_THREAD_PRIO);

If you have to cast the pointer to function to get pthread_create to compile, you have undefined behavior.

If the goal is to call a member function in a different thread, you need to wrap the call in an extern "C" function. This means no members and no templates; in the simplest case:

extern "C" void*
startThread( void* p )
{
    static_cast<T*>(p)->f();
}

and pass the address of startThread as third argument and a pointer to the object as fourth. If inheritance is involved, you must ensure that the fourth argument has the same type as that in the cast in startThread , eg:

pthread_create( &tmp, nullptr, &startThread, static_cast<Base*>( pointerToDerived ) );

if startThread casts to Base* .

If you need arguments to the function as well, you need to pass a pointer to a struct with both the pointer to the object and the additional arguments. You also need to ensure that the lifetime of this struct is sufficient, so that there is no risk of the thread accessing an already inexistant object. This often means an additional conditional variable, to ensure that the thread calling pthread_create doesn't continue before the new thread has made a copy of all of the relevant data. (Both Boost threads and the C++11 threads do this for you. It's only necessary if you need additional data, other than just the pointer to the object, in the new thread.)

This can get painful if you need to do it for many different types, and downright impossible if the class in question is a template. In such cases, one common solution is to use a Thread object, along the lines of:

class Thread
{
public:
    virtual void* run() = 0;
};

and a starter function:

namespace {
extern "C" void*
doStartThread( void* p )
{
    return static_cast<Thread*>( p )->run();
}
}

pthread_t
startThread( Thread* thread )
{
    pthread_t results;
    if ( pthread_create( &results, nullptr, doStartThread, thread ) != 0 ) {
        throw std::runtime_error( "Could not create thread" );
    }
}

Afterwards, you inherit from Thread , overriding the run function with whatever you want (and adding any additional data you might need); the derived class can even be a template.

Again, the lifetime of the Thread object is an issue; the solution I've usually used has been to require it to be dynamically allocated, and then delete it at the end of doStartThread . It's a very good idea to catch it in an std::unique_ptr in doStartThread , although you still want to catch exceptions in this function, since otherwise they will kill the process. And don't forget the delete if pthread_create fails (since the caller has passed over ownership. If you really want to be sure:

namespace {
extern "C" void*
doStartThread( void* p )
{
    std::unique_ptr<Thread*> object( static_cast<Thread*>( p ) );
    try {
        return object->run();
    } catch ( ... ) {
        return somethingElseToReportTheError;
    }
}
}

pthread_t
startThread( std::unique_ptr<Thread> thread )
{
    pthread_t results;
    if ( pthread_create( &results, nullptr, doStartThread, thread.get() ) != 0 ) {
        throw std::runtime_error( "Could not create thread" );
    }
    thread.release();   //  AFTER the call has succeeded!
}

I've used this technique successfully in a number of applications (using std::auto_ptr , since there was no std::unique_ptr then); typically, the fact that you need to use dynamic allocation is not an issue, and it solves the lifetime issue quite nicely. (The alternative would be to use a conditional variable, blocking the original thread until the new thread had copied everything over.)

Note that by using a unique_ptr in the interface, you effectively block the calling thread from further access to the thread object, by robbing it of its pointer to the object. This offers an additional guarantee with regards to thread safety. But of course, this additional guarantee (and the solution to the lifetime issues) only applies to the Thread object itself, and not to anything it might point to.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM