You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

双存储-加载原子操作场景下的内存顺序选择:如何确保线程1读取到b=1或线程2读取到a=1?

避免双0结果的内存顺序选择

嘿,这个问题是并发内存模型里的经典场景,咱们一步步拆解清楚:

首先明确我们要杜绝的坏情况:线程1读取到b=0,同时线程2读取到a=0——也就是所谓的“双0”结果。

为什么不能用memory_order_relaxed?

松弛内存顺序是最宽松的原子操作顺序规则,编译器和CPU可以自由重排同一线程内的原子操作(只要不违反线程内部的执行逻辑)。比如:

  • 线程1可能被重排成先读b(拿到0),再写a;
  • 线程2可能被重排成先读a(拿到0),再写b。

这种重排直接导致双0结果,而且松弛顺序下没有跨线程的同步关系,一个线程的写操作不一定会被另一个线程及时看到,进一步加剧了这种风险。

可行的两种方案

方案1:顺序一致内存顺序(memory_order_seq_cst)

这是最直观、最可靠的选择,所有标记为memory_order_seq_cst的原子操作会遵循一个全局统一的线性执行顺序——就像整个系统里的所有原子操作都按某一条时间线依次执行。

对这个场景来说,全局顺序里必然会出现以下两种情况之一:

  • 线程1的写a=1操作发生在线程2的读a操作之前:线程2读a时会拿到1;
  • 线程2的写b=1操作发生在线程1的读b操作之前:线程1读b时会拿到1。

绝对不会出现“线程1先读b、线程2先读a”都发生在对方写操作之前的情况,因为全局顺序是线性的。这个方案的唯一缺点是性能稍逊(CPU需要做更多同步工作),但胜在简单易懂,适合对性能要求不极致的场景。

方案2:Release-Acquire内存顺序对(memory_order_release + memory_order_acquire)

这是更轻量的高性能方案,具体做法是:

  • 线程1的原子写a=1用memory_order_release;
  • 线程1的原子读b用memory_order_acquire;
  • 线程2的原子写b=1用memory_order_release;
  • 线程2的原子读a用memory_order_acquire。

它能避免双0的核心原因有两点:

  1. 禁止线程内重排:release写会阻止任何后续操作被重排到该写操作之前;acquire读会阻止任何之前的操作被重排到该读操作之后。也就是说:
    • 线程1里,写a一定发生在读b之前;
    • 线程2里,写b一定发生在读a之前。
      直接杜绝了“先读再写”的重排导致的双0。
  2. 建立跨线程同步关系:如果线程2用acquire读a时拿到了1,那么线程1的release写a就和线程2的acquire读a建立了「同步-先行」关系——线程1写a之前的所有操作(这里就是写a本身)都会被线程2读a之后的操作可见。反过来,如果线程1用acquire读b拿到了1,线程2的release写b也会和线程1的acquire读b建立同步关系。

这种方案在保证正确性的同时,比顺序一致顺序的性能损耗更小,适合对性能敏感的场景。

总结

  • 追求简单可靠:选memory_order_seq_cst;
  • 追求高性能:选memory_order_release和memory_order_acquire的配对方案。

内容的提问来源于stack exchange,提问作者uncertainty principle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 20:42:33