对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. 标记1执行,x为true
  2. 标记2执行,y为true
  3. CPU1将y写入cache或者memory,CPU3可以看见改值
  4. 标记3执行,y为true
  5. 标记4执行,cache中的x为false,z为0
  6. 标记5执行,触发断言

当然你也可以用以下执行顺序理解上述代码(由于std::memory_order_relaxed在不同变量间不具有happens-before关系,因此,标记2可以在标记1之前执行), 故也可能存在如下执行顺序:

  1. 标记2执行,y为true
  2. 标记3执行,y为true
  3. 标记4执行,x为false,z为0
  4. 标记1执行,x为true
  5. 标记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的值一定是把前面的操作都执行完了之后的最新的值

posted @ 2026-08-10 23:02  大黑耗  阅读(2)  评论(0)    收藏  举报