图解Modern Cpp内存序

C++ 内存序(Memory Order)定义了多线程环境下对共享内存的操作顺序和可见性规则. 它是 C++ 原子操作(std::atomic)的重要组成部分, 允许开发者在性能和线程安全之间进行权衡.


为什么需要内存序?

在多线程编程中, 现代 CPU 和编译器会进行指令重排序, 以优化性能. 指令重排序可能导致线程间的操作顺序与程序逻辑不一致, 从而引发数据竞争. C++ 的内存序通过提供一组规则, 确保线程间操作的正确性, 同时尽量减少性能损失.


内存序的分类

C++ 提供了六种内存序, 分别针对不同的同步需求和性能优化:

内存序描述使用场景
memory_order_relaxed不保证操作的顺序, 仅保证原子性性能优先, 不需要顺序约束
memory_order_consume对数据依赖操作可见(目前等价于 acquire)数据依赖同步(罕见使用)
memory_order_acquire确保当前操作之前的读取有效, 阻止后续操作重排到前面多线程读取, 确保前置可见性
memory_order_release确保当前操作之后的写入有效, 阻止前序操作重排到后面多线程写入, 确保后置可见性
memory_order_acq_rel同时具备 acquirerelease 的效果读写同步
memory_order_seq_cst全局顺序一致性, 最严格的模式默认模式, 简单但性能最低

内存序的作用

指令重排序限制

跨线程可见性


样例分析

完全乱序: memory_order_relaxed

允许线程独立执行, 不保证顺序或可见性, 仅保证操作原子性.

#include <atomic>
#include <iostream>
#include <thread>

int main() {
  std::atomic<int> x(1);
  std::atomic<int> y(2);

  std::jthread t1([&]() {
    y.store(4, std::memory_order_relaxed);
    x.store(3, std::memory_order_relaxed);
  });

  std::jthread t2([&]() {
    std::cout << x.load(std::memory_order_relaxed) << std::endl;
    std::cout << y.load(std::memory_order_relaxed) << std::endl;
  });

  return 0;
}

可能有如下 4 种输出:

xy可能的执行顺序
12
c1
32
c2
14
c3
34
c4

读写同步: memory_order_acquirememory_order_release

用于线程间同步, 确保操作的可见性和顺序.

#include <atomic>
#include <iostream>
#include <thread>

int main() {
  std::atomic<int> x(0);
  std::atomic<int> y(0);

  std::jthread t1([&]() {
    y.store(2, std::memory_order_release);
    x.store(1, std::memory_order_release);
  });

  std::jthread t2([&]() {
    std::cout << x.load(std::memory_order_acquire) << std::endl;
    std::cout << y.load(std::memory_order_acquire) << std::endl;
  });

  return 0;
}

可能的执行结果:

xy可能的执行顺序
12
c1
14
c2
34
c3

一致性 memory_order_seq_cst

最严格的内存序, 确保全局顺序一致性.

#include <atomic>
#include <thread>
#include <iostream>

std::atomic<int> x(0), y(0);

void thread1() {
    x.store(1, std::memory_order_seq_cst);
    std::cout << "y: " << y.load(std::memory_order_seq_cst) << std::endl;
}

void thread2() {
    y.store(1, std::memory_order_seq_cst);
    std::cout << "x: " << x.load(std::memory_order_seq_cst) << std::endl;
}

int main() {
    std::thread t1(thread1), t2(thread2);
    t1.join();
    t2.join();
    return 0;
}

可能的执行步骤

xy可能的执行顺序
34
c1
32
c2

结果: 输出顺序严格受控, 但性能最低.


6. 内存序的权衡


总结