虚函数和pthread_create之间的竞争

Joe*_*ams 1 c++ pthreads race-condition

当我尝试使用虚方法创建一个类实例并将其传递给pthread_create时,我得到一个竞争条件,导致调用者有时会调用基本方法而不是像它应该的那样调用派生方法.谷歌搜索后pthread vtable race,我发现这是一个相当着名的行为.我的问题是,什么是解决它的好方法?

以下代码在任何优化设置中都表现出此行为.请注意,MyThread对象在传递给pthread_create之前已完全构造.

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Thread {
    pthread_t thread;

    void start() {
        int s = pthread_create(&thread, NULL, callback, this);
        if (s) {
            fprintf(stderr, "pthread_create: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
    }
    static void *callback(void *ctx) {
        Thread *thread = static_cast<Thread*> (ctx);
        thread->routine();
        return NULL;
    }
    ~Thread() {
        pthread_join(thread, NULL);
    }

    virtual void routine() {
        puts("Base");
    }
};

struct MyThread : public Thread {
    virtual void routine() {

    }
};

int main() {
    const int count = 20;
    int loop = 1000;

    while (loop--) {
        MyThread *thread[count];
        int i;
        for (i=0; i<count; i++) {
            thread[i] = new MyThread;
            thread[i]->start();
        }
        for (i=0; i<count; i++)
            delete thread[i];
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Ark*_*nez 5

这里唯一的问题是你在生成的线程执行方法之前删除了对象,所以那时子析构函数已经被触发并且对象不再存在了.

因此它与pthread_create或其他什么无关,它是你的时间,你不能产生一个线程,给它一些资源并在他有机会使用之前删除它们.

试试这个,它将展示在生成的线程使用它们之前主线程如何破坏objs:

struct Thread {
pthread_t thread;
bool deleted;

void start() {
    deleted=false;
    int s = pthread_create(&thread, NULL, callback, this);
    if (s) {
            fprintf(stderr, "pthread_create: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
    }
}
static void *callback(void *ctx) {
    Thread *thread = static_cast<Thread*> (ctx);
    thread->routine();
    return NULL;
}
~Thread() {
    pthread_join(thread, NULL);
}

virtual void routine() {
    if(deleted){
        puts("My child deleted me");
    }
    puts("Base");
}
};

struct MyThread : public Thread {
virtual void routine() {

}
~MyThread(){
    deleted=true;
}

};
Run Code Online (Sandbox Code Playgroud)

另一方面,如果你只是在删除它们之前在main中放置一个睡眠,那么你将永远不会遇到这个问题,因为生成的线程正在使用有效的资源.

int main() {
const int count = 20;
int loop = 1000;

while (loop--) {
    MyThread *thread[count];
    int i;
    for (i=0; i<count; i++) {
            thread[i] = new MyThread;
            thread[i]->start();
    }
    sleep(1);
    for (i=0; i<count; i++)
            delete thread[i];
}

return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 托梅克是对的.添加"sleep"对于竞争条件来说永远不是一个强有力的解决方案.在睡眠期间,无法保证那些其他线程将*完全*运行:也许机器正在运行一些其他程序,这恰好在整整一秒钟内耗尽.如果你想检查一个线程是否已经完成,那么这就是join的作用.没有别的事. (3认同)
  • 是.由于睡眠(1)线程在删除线程[i]之前完成,因此更难以达到竞争状态.但并非不可能.在调用析构函数之前,线程例程()没有完成(甚至启动)时,你仍然可以得到这种情况.所以它会尝试使用部分破坏的对象来触发问题.正确的解决方案是在调用pthread_join确认线程完成后才销毁对象. (2认同)