理解 C++ 原子序:并发世界的隐形契约
2025-06-05
我个人不太喜欢写多线程代码,太不可控了,而且遇到问题很难排查。
人习惯的思考方式,更像是单线程,直来直去,把一个问题想清楚。
可惜现代计算机不是。它脑子太多了(Multi-Core),如果只用其中一个,会造成巨大的浪费。
所以多线程代码,是一定要学习怎么写的。
1. 为什么需要原子序?
在这个过程中,有一个概念一定会遇到,就是原子性(Atomic)。
原子本意,是不可分割,一个整体,不存在中间状态。
可惜,很多看起来像是原子的操作,其实并不是。
例如:
a++;
尽管看上去只有一行,但实际的硬件执行流程,是先从内存中读取变量 a
,然后在寄存器中 +1,最后写回到内存。
当多个线程交叉运行时,原子性被打破,导致最后的结果不确定。
C++ 11 中引入了 std::atomic
,简化原子操作,之前 文章🔗 文章提到过。
std::atomic
保证了单一操作的原子性,但问题并没有完全解决。因为编译器和 CPU 可能对指令重排(reordering),在多线程场景中看到的值并非想要的顺序。
举例来说:
// 线程 Ax.store(1, std::memory_order_relaxed);y.store(1, std::memory_order_relaxed);
// 线程 Bif (y.load(std::memory_order_relaxed) == 1) { std::cout << x.load(std::memory_order_relaxed) << std::endl;}
x == 1
一定成立吗?
不一定。
编译器/CPU 可能先执行 y.store
,后执行x.store
。
可见理解 C++ 中的原子序(memory order),对于写出正确的代码同样重要。
2. 原子序有哪些?
原子序一共有 6 个。
由强到弱分别是:
内存序 | 含义 | 场景 |
---|---|---|
memory_order_seq_cst | 最强顺序,操作全局串行化 | 默认值 |
memory_order_acquire | 加载时获取之前写入的副作用 | 用于读取 flag/锁 |
memory_order_release | 存储时推送之前的写入 | 用于写 flag/锁 |
memory_order_acq_rel | 同时满足 acquire + release | 用于读改写操作 |
memory_order_relaxed | 无顺序要求,只保证原子性 | 仅在非常了解场景时使用 |
memory_order_consume | 理论存在,但编译器基本不实现 | 不要用 |
需要注意的地方有三点:
memory_order_seq_cst
是默认原子序,关闭指令重排,缺点是可能对性能造成一定影响memory_order_release
配合memory_order_acquire
是常见组合- 单一变量的操作场景,可以用
memory_order_relaxed
对于第二点,可以这样实现一个消息通知。
// 线程 A:写数据 + 通知data = 42;flag.store(true, std::memory_order_release);
// 线程 B:等待通知 + 读数据if (flag.load(std::memory_order_acquire)) { std::cout << data << std::endl; // 一定看到 42}
对于第三点,因为无法保证没有重排,因此只能用于单一变量的操作,像实现一个计数器。
counter.fetch_add(1, std::memory_order_relaxed);
3. 启发
仅用几个简单的原则,就可以判断选择何种内存序。
需求 | 建议使用 |
---|---|
不需要顺序、只要原子性 | relaxed |
一个线程写,一个线程读 | release + acquire |
同时读写,操作复杂 | acq_rel |
不确定,想安全 | seq_cst (默认) |
没有一个放诸四海而皆准的方法。
要针对具体场景,选择最合适的内存序。
4. 总结
我平时有这么一个习惯。
希望找到一个普适的、通用的办法,以应对所有的问题。
就像经济学中定义的很多概念,万千世界复杂,都想用一套理论解释。
哈佛大学经济史家格申克龙泼了一盆冷水:社会科学总渴望发现一套‘放之四海而皆准’的方法和规律,但这种心态需要成熟起来。不要低估经济现实的复杂性,也不要高估科学工具的质量。
软件工程里同样有这样的问题,trade-off (权衡)无处不在,找到十全十美的解法往往是困难的。具体问题具体分析,很朴素的道理,却往往容易被忽略。
(完)
参考
- 本文作者:Plantree
- 本文链接:https://plantree.me/blog/2025/c-memory-order/
- 版权声明:所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
最后更新于: 2025-06-05T01:00:58+08:00