关于memory_order_relaxed的线程重排疑问及书与演讲的矛盾
对比Anthony Williams所著《C++ Concurrency in Action》与某演讲内容时,发现二者对std::memory_order_relaxed的描述存在矛盾。
演讲中的示例代码如下:
// 主线程 std::atomic<int> x(0), y(0); // 线程1 int r1 = x.load(std::memory_order_relaxed); y.store(r1, std::memory_order_relaxed); // 线程2 int r2 = y.load(std::memory_order_relaxed); x.store(42, std::memory_order_relaxed);
演讲者称r1=r2=42是合法结果,理由是编译器可以自由重排线程2的指令;但书中提到不同变量的relaxed操作需遵循线程内的happens-before关系,似乎不允许此类重排。此外书中的“隔间隐喻”也无法解释该示例中出现非初始值的情况。
现提出两个问题:
- 同一线程内的relaxed操作是否允许重排?
- 如何调和书籍与演讲的内容差异?
问题1:同一线程内的relaxed操作是否允许重排?
是的,同一线程内针对不同原子变量的memory_order_relaxed操作允许被编译器或CPU重排,只要不违反单线程的“as-if”规则(即单线程执行结果与程序顺序执行结果一致)。
C++标准中,线程内的happens-before关系基于程序顺序,但memory_order_relaxed的语义本身不提供跨线程同步,也不强制线程内操作的执行顺序与程序顺序完全一致——唯一的硬约束是:单线程内对同一个原子变量的操作必须遵循程序顺序(比如对x的两次relaxed操作不能重排),但不同原子变量的relaxed操作之间没有顺序强制。
回到示例中的线程2:y.load(relaxed)和x.store(42, relaxed)是针对不同变量的操作,完全可以被重排为x.store(42)先执行,再执行y.load(),这就可能导致线程1读取到x的42值,进而让r1=r2=42成为合法结果。
问题2:如何调和书籍与演讲的内容差异?
《C++ Concurrency in Action》中提到的“线程内的happens-before关系”,本质是指单线程内的操作在逻辑上的先后顺序对本线程是可见的,但这并不等同于“操作不能被重排”——重排是编译器/CPU的优化行为,只要单线程的最终执行结果和程序顺序执行的结果一致,就符合标准。
书中的“隔间隐喻”是为了简化解释relaxed操作不提供跨线程同步的特性(每个线程有自己的“隔间”,操作不会主动同步到其他隔间),但这个隐喻没有覆盖“线程内操作重排”的场景。而演讲者的描述更聚焦于relaxed操作的底层优化可能性,二者的核心观点并不矛盾:
- 书籍强调的是单线程内操作的逻辑正确性(比如线程1中
x.load的结果一定会被y.store使用,不会因为重排导致y.store拿到无效值); - 演讲者强调的是跨线程视角下,不同变量的relaxed操作顺序可能被打乱,从而出现看似“反直觉”的结果(比如
r1=r2=42)。
简单来说,书籍的描述从单线程逻辑正确性角度出发,演讲则从跨线程内存可见性角度阐述relaxed操作的宽松特性,二者都符合C++标准,只是侧重点不同。
内容的提问来源于stack exchange,提问作者MaPo

