线程中指向这个*的指针

Pointer to this* in thread

本文关键字:指针 线程      更新时间:2023-10-16

我有一个类和一个库(lwip)。由于某些原因,我需要调用库的线程创建函数,如:

/** 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);

在这个函数中我们调用pthread:

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

我的调用看起来像:

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

我的类方法工作得很好,但我需要改变当前类的一些字段(如'状态')或者)我有一个想法传递一个指针到当前类的线程,并在线程函数改变类字段。Some kind:

 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;
}

差不多。但似乎我做错了什么-

     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;

这里有两个问题:如果main_thread成员函数是静态成员函数,使用&lwip::main_thread传递指向它的指针,则不需要强制转换。如果函数不是 static,则必须设置为static

另一个问题是,如果你传递给线程函数的实例(this)是被销毁的,线程函数现在有一个指向被销毁对象的指针。小心使用临时对象或按值传递实例。


如果实际的线程函数不能是static,你可以很容易地解决它与一个静态包装器函数:

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);

如果必须将指针强制转换为函数来获取pthread_create编译时,你有未定义的行为。

如果目标是在另一个线程中调用成员函数,您需要将调用封装在extern "C"函数中。这意味着没有成员和模板;在最简单的情况下:

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

并传递startThread的地址作为第三个参数和指向对象的指针作为第四个。如果涉及到继承,必须确保第四个参数的类型与在startThread中,例如:

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

如果startThread强制转换为Base*

如果你也需要函数的参数,你需要传递指向结构体的指针,其中包含指向对象的指针和附加参数。您还需要确保此结构体的生命周期是足够的,因此没有风险访问已经不存在的对象的线程。这通常意味着额外的条件变量,以确保调用pthread_create的线程在新线程已经复制了所有相关数据。(包括Boost线程和c++ 11线程为您完成了这项工作。这只是如果您需要其他数据,则需要指向新线程中对象的指针)

如果您需要为许多不同的类型,如果所讨论的类是一个模板。在这种情况下,一个常见的解决方案是使用一个Thread对象,沿着以下行:

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

和一个启动函数:

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" );
    }
}

之后,继承Thread,覆盖run函数中添加您想要的任何内容(并添加任何附加数据)你可能需要);派生类甚至可以是模板。

同样,Thread对象的生命周期是一个问题;的我通常使用的解决方案是要求它是动态分配,然后在结束时删除它doStartThread。这是一个很好的主意抓住它在一个doStartThread中的std::unique_ptr,尽管您仍然想要在这个函数中捕获异常,否则它们会终止这个过程。不要忘记delete ifpthread_create失败(因为调用者已经通过)所有权。如果你真的想确定:

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!
}

我已经成功地在许多应用程序(使用std::auto_ptr,因为没有std::unique_ptr然后);通常,事实是你需要使用动态分配不是问题,而且它解决了终身问题非常好。(另一种选择是使用一个条件变量,阻塞原始线程,直到新线程已经复制了所有内容

注意,通过在接口中使用unique_ptr,您可以有效地阻止调用线程进一步访问线程对象,通过剥夺其指向该对象的指针。这提供关于线程安全的额外保证。但当然,这额外的保证(和解决方案)(生存期问题)只适用于Thread对象本身,而不是指向的任何东西