0

在这个示例程序中,我试图避免使用前向声明和循环依赖来利用 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;
}

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

4

2 回答 2

6

您将对函数本地的引用传递给flambda body,它由thread构造函数调用。

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

稍微扩展一下:

创建的新线程run将执行 的副本,这是一个包含对对象的引用 ( )body的 lambda ,该对象具有函数的作用域,并且在主线程离开时将被销毁。[&f]frunrun

线程函数将调用它在行中operator()的引用。如果主线程在线程函数执行此调用之前到达范围结束,则此调用已经导致未定义的行为。这里的数据竞争是,主线程可能会写入访问内存来破坏它,与衍生线程中对内存的读取访问不同步。ff(this)bodyrunff

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

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

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

这会将外部 lambdadata_race作为引用,将此引用复制到body其中,只要您确保data_raceinmain超过所有线程,就可以避免前面提到的数据竞争。

您编辑的代码做了类似的事情,即它消除了本地对象runainbody将是对A定义的引用main,只要main保证其生命周期超出线程的生命周期,此时就不会导致任何问题。

于 2019-08-31T19:58:56.867 回答
0

数据竞争是由于所有不同对象共享一个A对象(在 main 中声明) ,因为它是通过对lambda 的引用传递的。在 中创建的所有线程中都引用了这个对象,因此分配取决于最后执行的线程,因此存在数据竞争。aBdata_raceAB::runx = b->x

于 2019-08-31T20:57:08.903 回答