ThreadSanitizer检测到数据争用,问题出在哪里?

joy*_*tic 0 c++ concurrency multithreading c++11 data-race

在此示例程序中,我试图避免利用lambda函数(称为data_race)来使用前向声明和循环依赖关系

struct B{
    int x;
    std::thread* tid;

    B(int _x){
        x = _x;
        tid = NULL;
    }

    ~B(){
        if(tid != NULL) delete tid;
    }

    void run(std::function<void(B*)> f){
        auto body = [&f, this](){
            f(this);
        };

        tid=new std::thread(body);
    }

    void wait(){
        tid->join();
    }
};

struct A{
    int x;
    std::mutex mtx;

    A(int _x){
        x = _x;
    }

    void foo(B* b){
        std::unique_lock<std::mutex> lock(mtx);
        x = b->x;
    };
};

int main(){
    A a(99);
    std::vector<B> v;

    auto data_race = [&a](B* b){ a.foo(b);};

    for(int i=0; i<10; i++){
        v.push_back(B(i));
    }

    for(int i=0; i<v.size(); i++){
        v[i].run(data_race);
    }

    for(int i=0; i<v.size(); i++){
        v[i].wait();
    }

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

但是,ThreadSanitizer会检测到来自lambda函数data_race的数据争用。你能帮我理解为什么吗?A中的互斥体应该足以避免这种情况。另外,您能帮我找到解决方案吗?


编辑:使用转发声明,不再检测到数据争用,为什么?(仅发布结构,不发布主要结构,对冗长的帖子表示抱歉)

struct B;
struct A{
    int x;
    std::mutex mtx;

    A(int _x){
        x = _x;
    }

    void foo(B* b);
};

struct B{
    int x;
    std::thread* tid;

    B(int _x){
        x = _x;
        tid = NULL;
    }

    ~B(){
        if(tid != NULL) delete tid;
    }

    void run(A& a){
        auto body = [&a, this](){
            a.foo(this);
        };

        tid=new std::thread(body);
    }

    void wait(){
        tid->join();
    }
};

void A::foo(B* b){
    std::lock_guard<std::mutex> lock(mtx);
    x = b->x;
}

Run Code Online (Sandbox Code Playgroud)

wal*_*nut 6

您正在将对函数本地的引用传递f给lambda body,该引用由thread构造函数调用。

当线程函数到达内部调用时,该对象可能不再存在body

扩大一点:

由创建的新线程run将执行的副本body,该副本是包含[&f]对象引用()的lambda ,该对象f具有功能范围,run并且在主线程离开时将被销毁run

线程函数将operator()f中的行f(this)中对其进行调用body。如果应该run在线程函数执行此调用之前在主线程中到达作用域末端,则此调用已经引起了不确定的行为。此处的数据争用是,主线程可能写入的内存f以破坏它,f而与生成的线程中对内存的读取访问不同步。

可以完全避免使用中间函数对象:

template<typename F>
void run(const F& f){
    auto body = [this](){
        f(this);
    };

    tid=new std::thread(body);
}
Run Code Online (Sandbox Code Playgroud)

这将以外部lambda data_race作为参考,将此参考复制到body其中,并且只要确保data_racein main不在所有线程中使用,就可以避免前面提到的数据争用。

您编辑的代码执行类似的操作,即消除了本地的对象runain body是对A定义的引用,在该定义中main,只要main保证其寿命超出线程的寿命,就不会在此时造成任何问题。