C++多线程安全性探讨:memory_order_relaxed下断言是否失效?
案例
案例1
根据GCC文档,以下场景中的断言不会失效:
// 线程1 x.store(1, memory_order_relaxed); x.store(2, memory_order_relaxed); // 线程2 y = x.load(memory_order_relaxed); z = x.load(memory_order_relaxed); assert(y <= z);
该断言不会失效的原因是:对于同一个原子变量的relaxed加载,一旦观测到某个后续存储的值,就不会再观测到更早存储的值。这一规则避免了对同一变量的relaxed加载被不合理合并,同时保证一个线程的relaxed存储会在合理时间内被其他线程的relaxed加载观测到(非缓存一致性架构上会自动处理缓存刷新,且刷新可在多个relaxed操作间合并)。relaxed模式最适合仅需要保证变量操作原子性,不需要同步其他共享内存数据的场景,比如简单的统计计数器。
案例2
案例2中的断言是可能失效的,并非不会失效:
std::atomic<bool> x{false}, y{false}; void thread1() { x.store(true, std::memory_order_relaxed); // (1) y.store(true, std::memory_order_relaxed); // (2) } void thread2() { while (!y.load(std::memory_order_relaxed)); // (3) assert(x.load()); // (4) }
因为memory_order_relaxed不提供任何线程间的内存同步或执行顺序保证,编译器或CPU可能会重排线程1中(1)和(2)的执行顺序——也就是说,线程1可能先执行y的存储操作,再执行x的存储操作。当线程2通过循环检测到y为true时,线程1的x存储操作可能还未完成,此时x.load()会得到false,触发断言失败。
测试代码
#include <atomic> #include <thread> #include <cassert> std::atomic<bool> x{false}, y{false}; void thread1() { x.store(true, std::memory_order_relaxed); // (1) y.store(true, std::memory_order_relaxed); // (2) } void thread2() { while (!y.load(std::memory_order_relaxed)); // (3) assert(x.load()); // (4) } void fn() { int i = 0; // 修正原代码未初始化的问题 while(i < 10000000) { x.store(false, std::memory_order_relaxed); y.store(false, std::memory_order_relaxed); std::thread t1(thread1); std::thread t2(thread2); t1.join(); t2.join(); i++; } } int main() { fn(); return 0; }
关于memory_order_relaxed的安全性总结
memory_order_relaxed是安全的,但仅局限于原子操作本身的原子性——它能保证对原子变量的读写操作不会被打断,不会出现部分更新的情况。但它不提供任何线程间的内存可见性或执行顺序保证:不同线程对不同原子变量的relaxed操作之间没有同步关系,编译器和CPU可以自由重排这些操作的顺序。
因此,如果你需要依赖多个操作在不同线程间的执行顺序或可见性,memory_order_relaxed是不适用的,需要使用更强的内存序(比如memory_order_release/memory_order_acquire、memory_order_seq_cst等)。
内容的提问来源于stack exchange,提问作者xing franics

