ThreadSanitizer 检测到数据竞争,问题出在哪里?
ThreadSanitizer detects a data race, where is the problem?
在此示例程序中,我试图避免使用前向声明和循环依赖利用 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 内部的互斥体应该足以避免它。还有,你能帮我想个办法吗?
编辑:使用前向声明,不再检测到数据竞争,为什么? (只发结构体,不发主体,抱歉长了post)
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;
}
您正在将对局部函数 f
的引用传递给 lambda body
,它由 thread
构造函数调用。
当线程函数到达内部调用时,此对象可能不再存在 body
。
展开一点:
run
创建的新线程将执行 body
的副本,这是一个包含对对象 f
的引用 ([&f]
) 的 lambda,它具有函数run
的作用域,当主线程离开run
.
时会被销毁
线程函数将在 body
中的 f(this)
行中引用 f
时调用 operator()
。如果主线程中的 run
在线程函数执行此调用之前到达作用域末尾,则此调用已经导致未定义的行为。这里的数据竞争是,主线程可能会写入 f
的内存以销毁它,与生成线程中对 f
的内存的读取访问不同步。
完全可以避免中间函数对象:
template<typename F>
void run(const F& f){
auto body = [this](){
f(this);
};
tid=new std::thread(body);
}
这将以外层lambda data_race
作为参考,将此参考复制到body
中,只要确保main
中的data_race
是out -live所有线程,避免前面提到的数据竞争。
您编辑的代码做了类似的事情,即它消除了 run
的本地对象。 body
中的 a
将是对 main
中定义的 A
的引用,只要 main
保证其生命周期超出线程的生命周期,此时不会造成任何问题。
数据争用是由于有一个 A
对象(a
,在 main 中声明),它被所有不同的 B
对象共享,因为它是通过引用传递的到 data_race
lambda。在 B::run
中创建的所有线程都引用了这个 A
对象,因此分配 x = b->x
取决于最后执行的线程,因此存在数据竞争。
在此示例程序中,我试图避免使用前向声明和循环依赖利用 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 内部的互斥体应该足以避免它。还有,你能帮我想个办法吗?
编辑:使用前向声明,不再检测到数据竞争,为什么? (只发结构体,不发主体,抱歉长了post)
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;
}
您正在将对局部函数 f
的引用传递给 lambda body
,它由 thread
构造函数调用。
当线程函数到达内部调用时,此对象可能不再存在 body
。
展开一点:
run
创建的新线程将执行 body
的副本,这是一个包含对对象 f
的引用 ([&f]
) 的 lambda,它具有函数run
的作用域,当主线程离开run
.
线程函数将在 body
中的 f(this)
行中引用 f
时调用 operator()
。如果主线程中的 run
在线程函数执行此调用之前到达作用域末尾,则此调用已经导致未定义的行为。这里的数据竞争是,主线程可能会写入 f
的内存以销毁它,与生成线程中对 f
的内存的读取访问不同步。
完全可以避免中间函数对象:
template<typename F>
void run(const F& f){
auto body = [this](){
f(this);
};
tid=new std::thread(body);
}
这将以外层lambda data_race
作为参考,将此参考复制到body
中,只要确保main
中的data_race
是out -live所有线程,避免前面提到的数据竞争。
您编辑的代码做了类似的事情,即它消除了 run
的本地对象。 body
中的 a
将是对 main
中定义的 A
的引用,只要 main
保证其生命周期超出线程的生命周期,此时不会造成任何问题。
数据争用是由于有一个 A
对象(a
,在 main 中声明),它被所有不同的 B
对象共享,因为它是通过引用传递的到 data_race
lambda。在 B::run
中创建的所有线程都引用了这个 A
对象,因此分配 x = b->x
取决于最后执行的线程,因此存在数据竞争。