理解 C++ 原子序:并发世界的隐形契约

2025-06-05 pv

我个人不太喜欢写多线程代码,太不可控了,而且遇到问题很难排查。

人习惯的思考方式,更像是单线程,直来直去,把一个问题想清楚。

可惜现代计算机不是。它脑子太多了(Multi-Core),如果只用其中一个,会造成巨大的浪费。

所以多线程代码,是一定要学习怎么写的

1. 为什么需要原子序?

在这个过程中,有一个概念一定会遇到,就是原子性(Atomic)。

原子本意,是不可分割,一个整体,不存在中间状态。

可惜,很多看起来像是原子的操作,其实并不是。

例如:

a++;

尽管看上去只有一行,但实际的硬件执行流程,是先从内存中读取变量 a,然后在寄存器中 +1,最后写回到内存。

当多个线程交叉运行时,原子性被打破,导致最后的结果不确定

C++ 11 中引入了 std::atomic,简化原子操作,之前 文章🔗 文章提到过。

std::atomic 保证了单一操作的原子性,但问题并没有完全解决。因为编译器和 CPU 可能对指令重排(reordering),在多线程场景中看到的值并非想要的顺序

举例来说:

// 线程 A
x.store(1, std::memory_order_relaxed);
y.store(1, std::memory_order_relaxed);
// 线程 B
if (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理论存在,但编译器基本不实现不要用

需要注意的地方有三点:

  1. memory_order_seq_cst 是默认原子序,关闭指令重排,缺点是可能对性能造成一定影响
  2. memory_order_release 配合 memory_order_acquire 是常见组合
  3. 单一变量的操作场景,可以用 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 (权衡)无处不在,找到十全十美的解法往往是困难的。具体问题具体分析,很朴素的道理,却往往容易被忽略。

(完)

参考

  1. std::memory_order - cppreference.com🔗
在 GitHub 上编辑本页面

最后更新于: 2025-06-05T01:00:58+08:00