关于C++11/14中memory_order_relaxed悬空值循环依赖的咨询
背景说明
我对cppreference中Relaxed ordering的如下描述存在困惑:
即使使用宽松内存模型,也不允许凭空出现的值循环依赖于自身的计算。例如,当x和y初始值为0时,
// Thread 1: r1 = y.load(std::memory_order_relaxed); if (r1 == 42) x.store(r1, std::memory_order_relaxed); // Thread 2: r2 = x.load(std::memory_order_relaxed); if (r2 == 42) y.store(42, std::memory_order_relaxed);不允许出现r1 == r2 == 42的结果,因为向y存储42的操作依赖于向x存储42,而后者又循环依赖于向y存储42。注意:在C++14之前,规范在技术上允许这种情况,但不建议实现者这么做。
我使用以下代码进行了测试,运行约2小时后,未出现r1 == 42或r2 == 42的情况:
#include <iostream> #include <thread> #include <atomic> std::atomic<int> x(0); std::atomic<int> y(0); int r1 = 0; int r2 = 0; void foo1() { r1 = y.load(std::memory_order_relaxed); if (r1 == 42) { x.store(r1, std::memory_order_relaxed); } } void foo2() { r2 = x.load(std::memory_order_relaxed); if (r2 == 42) { y.store(42, std::memory_order_relaxed); } } int main() { while (1) { std::thread t1(foo1); std::thread t2(foo2); t1.join(); t2.join(); if (r1 == 42 || r2 == 42) { break; } } std::cout << "r1 " << r1 << ", r2 " << r2 << std::endl; return 0; }
测试环境:OS为Ubuntu 22.04.03 LTS (WSL2),G++版本11.4.0,编译命令为g++ -std=c++11 test.cpp -pthread
技术问题
- 上述注释中“在C++14之前,规范在技术上允许这种情况,但不建议实现者这么做”具体含义是什么?
- 是否意味着在C++11中,该代码有可能出现r1 == r2 == 42的结果?如果是,原因是什么?
解答
问题1解答
C++11的内存模型规范在定义"happens-before"关系和值依赖规则时存在表述漏洞,没有明确禁止这种循环依赖的无中生有值场景。从规范字面条款推导,r1和r2同时为42的结果并没有被严格排除,但标准委员会从一开始就明确不希望编译器或硬件实现真的允许这种情况发生,所以加上了"不建议实现者这么做"的提示。
C++14补全了规范中的漏洞,明确引入"无循环依赖"约束:任何值的计算都不能依赖于自身的结果,直接禁止了这种逻辑上不可能的循环依赖场景。
问题2解答
从C++11规范的字面规则看,理论上存在出现r1 == r2 == 42的可能性,但实际中几乎不会发生。
原因在于:C11的内存模型没有明确禁止编译器或硬件进行极端的指令重排或推测执行——比如,编译器可能推测r1会等于42,提前执行x.store(42);同时另一个线程也推测r2等于42,提前执行y.store(42),最终导致两个线程的加载操作都读到了对方提前存储的值。但这种推测执行完全违背程序的逻辑意图,所有主流编译器(比如GCC、Clang)从一开始就没有实现这种极端行为,即使在C11标准下也会避免这种情况。
你测试2小时没出现结果,也符合这个现实情况——主流实现不会真的触发这种理论上的漏洞场景。
内容的提问来源于stack exchange,提问作者zzzxyz

