int main() {
int f = 0, x=0;
std::thread *t = new std::thread([&f, &x](){ while( f == 0); std::cout << x << endl;});
std::thread *t2 = new std::thread([&f, &x](){ x = 42; f = 1;});
t->join();
t2->join();
return 0;
}
据我所知,理论上可以将stdout等于0
反对我们的直觉(因此我们期待42
。但是,CPU可以执行无序指令并且在事实上,可以按顺序执行程序:
(我们假设我们的CPU中有> 1个核心)
因此,第二个核心上的thread#2
首先执行(因为OOO meachanism)f = 1
然后,thread#1
在第一个核心上执行第一个程序:while( f == 0); std::cout << x << endl
。因此,输出为0
。
我试图得到这样的输出,但我总是得到42
。我运行该程序1000000次,结果总是相同= 42
。
(我知道它不安全,有数据竞争)。
我的问题是:
0
?f
,但我听说过有关内存栅栏的内容,请多说一遍。 答案 0 :(得分:4)
但是,CPU可以执行乱序指令,事实上,可以按顺序执行程序:
无序执行不同于加载/存储何时全局可见的重新排序。 OoOE保留了编程按顺序运行的错觉。没有OoOE就可以重新排序内存。即使是有序的流水线核心也希望缓冲其商店。请参阅parts of this answer, for example。
如果我是对的,是否可以强制将输出等于0?
不在x86上,which only does StoreLoad reordering,而不是StoreStore重新排序。如果是compiler reorders the stores to x
and f
at compile time,那么在看到x==0
之后,您有时会看到f==1
。否则你永远不会看到它。
在生成thread2之前产生thread1之后的短暂睡眠还会确保在修改它之前thread1在x
上旋转。那么你不需要thread2,并且实际上可以从主线程中进行存储。
看一下Jeff Preshing的Memory Reordering Caught In The Act,看一下在x86上观察运行时内存重新排序的真实程序,在Nehalem上每6k次迭代一次。
在一个弱有序的架构上,你可能会看到StoreStore在运行时重新排序,就像你的测试程序一样。但是你可能不得不安排变量在不同的缓存行中!而且你需要在一个循环中进行测试,而不是每个程序调用一次。
如何安全地使用此代码?我知道互斥锁/信号量,我可以用互斥锁保护f,但我听说过有关内存栅栏的内容,请多说一遍。
使用C++11 std::atomic获取f
对std::atomic<uin32t_t> f; // flag to indicate when x is ready
uint32_t x;
...
// don't use new when a local with automatic storage works fine
std::thread t1 = std::thread([&f, &x](){
while( f.load(std::memory_order_acquire) == 0);
std::cout << x << endl;});
// or sleep a few ms, and do t2's work in the main thread
std::thread t2 = std::thread([&f, &x](){
x = 42; f.store(1, std::memory_order_release);});
的访问权限。
f = 1
MFENCE
之类的默认内存排序是mo_seq_cst,它需要x86上的f
,或者其他架构上的等价昂贵的屏障。
在x86上,较弱的内存排序只会阻止编译时重新排序,但不需要任何屏障指令。
std :: atomic还可以阻止编译器将while
的加载从thread1中的volatile
循环中提取出来,就像@Baum的评论所描述的那样。 (因为atomic有像{{1}}这样的语义,假定存储的值可以异步更改。由于数据争用是未定义的行为,编译器通常可以从循环中提升负载,除非别名分析无法通过指针证明存储循环内部无法修改该值。)。