考虑这个例子:
#include <iostream>
#include <atomic>
#include <random>
#include <thread>
int need_close(){
random_device rd;
std::mt19937 gen(rd());
uniform_int_distribution<int> distribute(0, 2);
return distribute(gen);
}
void invoke(std::atomic<bool>& is_close){
if(is_close.load(std::memory_order::relaxed)){ // #1
return;
}
auto r = need_close();
if(r==0){
is_close.store(true,std::memory_order::relaxed);
}
}
int main(){
std::atomic<bool> is_close{false};
std::thread t1([&](){
for(auto i = 0; i<100000;i++)
invoke(is_close);
});
std::thread t2([&](){
for(auto i = 0; i<100000;i++)
invoke(is_close);
});
t1.join();
t2.join();
}
在此示例中,一旦线程看到
relaxed
(不是立即,而是在某个时间线程可以读取
need_close
),is_close == true
的顺序是否足以避免调用
is_close==true
?在此示例中,似乎我不需要同步来避免数据争用,因为此示例中没有冲突操作。但是,从编译器实现来看,编译器可能会将 #1
重新排序到它后面的任何位置,因为这里使用了 relaxed
,例如,如果 #1
移动到 need_close
的调用点之后的某个位置,则 即使
need_close
设置为
is_close
,true
仍将始终被再次调用,这是不期望的。所以,我想知道是否需要 Acquire/Release
排序以避免编译器重新排序代码以使逻辑符合预期?
您如何知道您是否可以前往拨打
need_close()
的分行?通过读取 is_close
中的值,在不知道该值的情况下不可能这样做,因此即使在宽松的排序约束下,编译器也无法在调用 need_close()
后对 #1 进行重新排序,但这并不能解决您的问题。
排序不会阻止函数在读取和
need_close()
之间被不同线程调用两次,另一个线程可以执行这两个操作,因此need_close()
将被调用两次。
您根本无法阻止
need_close()
被呼叫两次。通过使其逻辑并发友好或使用互斥体,它必须对并发调用是安全的。