std::memory_order_seq_cst工作原理及相关内存序示例疑问
先来说线程c和线程d可能看到不同结果的核心原因——这本质上是acquire/release内存序与sequential consistent(seq_cst)内存序的差异。
假设我们用的是acquire/release(而非seq_cst),对应场景是两个初始为0的原子变量x和y:
- 线程a执行
x.store(1, memory_order_release) - 线程b执行
y.store(1, memory_order_release) - 线程c先读
x(acquire),如果读到1再读y(acquire) - 线程d先读
y(acquire),如果读到1再读x(acquire)
acquire/release的规则是:一个release操作和后续对同一变量的acquire操作之间建立同步关系,但不同变量的操作之间没有强制的全局顺序。线程a的store和线程b的store之间没有任何同步,它们的执行顺序在全局视角里是“不确定”的。
所以可能出现这种情况:
- 线程c的
x.load读到了1(说明线程a的store已经完成),但此时线程b的store操作的结果还没同步到线程c所在的缓存,所以y.load读到0; - 同时线程d的
y.load读到了1(说明线程b的store已经完成),但线程a的store结果还没同步到线程d的缓存,所以x.load读到0。
这就是c和d看到不同结果的原因——acquire/release只保证单变量的同步链,没有强制所有线程看到一致的全局操作序列。而如果换成memory_order_seq_cst,所有seq_cst操作会遵循一个全局的总序,要么a的store在b之前,要么反过来,这种“各读各的”矛盾情况就不会发生。
再来说你提到的简单示例为什么z始终是3。假设场景是三个线程对初始为0的原子变量z执行z.fetch_add(1, ...),最终结果一定是3,这和内存序无关,核心是原子操作的“原子性”特性。
fetch_add是一个原子的读-改-写操作:它会读取当前z的值,加1,再把新值写回去,整个过程是不可分割的。哪怕线程之间没有任何内存序同步(比如用memory_order_relaxed),也不会出现两个线程同时读到z=0然后都写回1的情况——硬件层面的原子指令会保证这个操作的排他性。每个fetch_add都会完整地完成一次累加,三个操作下来,结果必然是3。你担心的“线程b看到线程a完成后的z仍为0”是不会发生的,因为fetch_add的原子性保证了,当线程a的操作完成后,z的值已经被原子地更新,线程b的fetch_add一定会读到更新后的值(或者在它之后的另一个中间值,但绝不会跳过累加步骤)。
内容的提问来源于stack exchange,提问作者Minee

