C++中memory_order_acq_rel与seq_cst的混淆排序及死锁问题
死锁的核心原因
你遇到的死锁本质是**std::memory_order_acq_rel不保证原子操作的全局总序**,而seq_cst会强制所有原子操作(包括其他seq_cst操作)遵循一个统一的全局执行顺序。
当flag.store(false, acq_rel)时,这个操作仅保证:
- 当前线程中,
data.dec()的结果在flag.store(false)之前可见(release语义); - 当前线程中,后续操作不会被重排到
flag.store(false)之前(acquire语义)。
但它不参与全局总序的约束,这就可能出现跨线程的操作顺序混乱:比如在多轮迭代中,dec_thread的flag.store(false)(acq_rel)和inc_thread的flag.store(true)(seq_cst)的全局执行顺序,可能与线程实际的执行顺序相反。dec_thread的下一次flag.load()(seq_cst)可能会读取到自己之前写入的false,完全看不到inc_thread已经写入的true,但此时data已经被inc_thread递增,最终导致dec_thread无限等待flag变为true,触发死锁。
为什么换成seq_cst就解决了问题
std::memory_order_seq_cst是最严格的内存序,它强制所有seq_cst原子操作遵循一个全局统一的总序,所有线程看到的操作顺序完全一致。
当flag.store(false)改为seq_cst后,所有对flag的操作(包括inc_thread的flag.store(true)和dec_thread的flag.load())都纳入了这个全局总序。这就保证了每一轮迭代的顺序严格符合预期:inc_thread的data.inc() → flag.store(true) → dec_thread的flag.load()(读到true) → data.dec() → flag.store(false) → 下一轮迭代的inc_thread操作
不会出现操作顺序颠倒导致的可见性丢失,因此不会死锁。
保证无竞争的最宽松内存序配置
要维持你的流程正确性,只需要建立关键的happens-before关系,不需要全用seq_cst。最宽松的配置如下:
线程分工与内存序选择
inc_thread:
- 等待
flag为false:使用flag.load(std::memory_order_acquire)—— 确保后续的data.inc()不会被重排到load之前,且能看到dec_thread写入的flag=false对应的所有操作。 - 递增
data:使用data.fetch_add(1, std::memory_order_relaxed)——data的修改仅需要通过flag的同步来保证可见性,不需要单独的内存序约束。 - 设置
flag为true:使用flag.store(true, std::memory_order_release)—— 确保data.inc()的结果在flag.store之前可见,且对读取到flag=true的线程同步。
- 等待
dec_thread:
- 等待
flag为true:使用flag.load(std::memory_order_acquire)—— 确保后续的data.dec()不会被重排到load之前,且能看到inc_thread写入的flag=true对应的所有操作。 - 递减
data:使用data.fetch_sub(1, std::memory_order_relaxed)—— 同理,依赖flag的同步保证可见性。 - 设置
flag为false:使用flag.store(false, std::memory_order_release)—— 确保data.dec()的结果在flag.store之前可见,且对读取到flag=false的线程同步。
- 等待
为什么这个配置可行
这套内存序通过release-acquire对建立了完整的同步链:
inc_thread的data.inc()happens beforeflag.store(true, release)flag.store(true, release)synchronizes withdec_thread的flag.load(true, acquire)dec_thread的flag.load(true, acquire)happens beforedata.dec()data.dec()happens beforeflag.store(false, release)flag.store(false, release)synchronizes withinc_thread的flag.load(false, acquire)- 最终形成循环的同步链,完全保证了操作的顺序和可见性,且内存序开销最小。
内容的提问来源于stack exchange,提问作者Jordan Woyak

