对std::memory_order_relaxed的理解
1 #include <thread> 2 #include <atomic> 3 #include <cassert> 4 5 std::atomic<bool> x, y; 6 std::atomic<int> z; 7 8 void write_x_then_y() { 9 x.store(true, std::memory_order_relaxed); // 1 10 y.store(true, std::memory_order_relaxed); // 2 11 } 12 13 void read_y_then_x() { 14 while (!y.load(std::memory_order_relaxed)) { // 3 15 /* code */ 16 } 17 18 if (x.load(std::memory_order_relaxed)) { //4 19 ++z; 20 } 21 22 } 23 24 int main(int argc, char* argv[]) { 25 26 x = false; 27 y = false; 28 z = 0; 29 30 std::thread t1(write_x_then_y); 31 std::thread t2(read_y_then_x); 32 t1.join(); 33 t2.join(); 34 assert(z.load() != 0); // 5 35 return 0; 36 }
上述代码,在x86架构的机器上跑,永远不会触发 5,但是若是ARM架构,则可能触发5。
关于该代码的理解,我们可以结合上述CPU架构小节来理解,假设线程t1运行在CPU1,线程t2运行在CPU3,
std::memory_order_relaxed在此处可以理解为仅仅保持原子性,没有其他的作用。因此线程1虽然更新x,y为true,但由于无法保证 两者都同时对其他CPU可见(每个CPU可能在任何时刻将其store buffer中的值写入cache或者memory,此时才有机会被其他CPU看见)。
因此上述可能存在如下执行顺序:
- 标记1执行,x为true
- 标记2执行,y为true
- CPU1将y写入cache或者memory,CPU3可以看见改值
- 标记3执行,y为true
- 标记4执行,cache中的x为false,z为0
- 标记5执行,触发断言
当然你也可以用以下执行顺序理解上述代码(由于std::memory_order_relaxed在不同变量间不具有happens-before关系,因此,标记2可以在标记1之前执行), 故也可能存在如下执行顺序:
- 标记2执行,y为true
- 标记3执行,y为true
- 标记4执行,x为false,z为0
- 标记1执行,x为true
- 标记5执行,触发断言
================================================以上内容转自 https://zhuanlan.zhihu.com/p/457440832
个人理解:
第一种说明好理解。
第二种:为什么标记2可以比标记1先执行,事实上,在cpu执行原子指令时,
对2个不同的原子分别进行的原子操作,本身就是乱序的,并不会是由于代码里先操作了a,再操作b,CPU就会按照同样的顺序去操作,不是的,CPU只保证同一个原子变量的操作顺序是和代码的执行顺序一致,
但不同的原子变量之间,CPU并不保证它们的操作顺序。例如,一个线程的代码对a,b2个原子的操作,代码是这样写的:a1;b1;a2;b2;a3;b3;a4;b4;a5;b5; 对于a本身,CPU的操作顺序一定是保证从1到5按顺序执行(同理b也是)。
但是把a,b的操作综合起来,其顺序可能是:a1;b1;b2;a2;b3;a3;a4;a5;b4;b5;
虽然对a,b的操作是乱序的,但是对单独一个线程本身而言,程序的执行并不会出错,因为CPU确保,若同一个线程内已经读取到某个值a,则后续的load不能读取到比a更老的值。因此无论程序执行到何处,拿到a或b的值一定是把前面的操作都执行完了之后的最新的值

浙公网安备 33010602011771号