图解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全局顺序一致性, 最严格的模式默认模式, 简单但性能最低

内存序的作用

指令重排序限制
  • relaxed: 允许指令完全重排序.
  • acquire: 禁止读取操作重排到当前操作之前.
  • release: 禁止写入操作重排到当前操作之后.
  • seq_cst: 严格禁止所有重排序.
跨线程可见性
  • acquire: 确保当前线程可以看到其他线程释放的内存操作.
  • release: 确保当前线程的操作对其他线程可见.

样例分析

完全乱序: 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可能的执行顺序
12c1
32c2
14在这里插入图片描述
34在这里插入图片描述

读写同步: memory_order_acquirememory_order_release

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

#include <atomic>
#include <iostream>
#include <syncstream>  // c++20
#include <thread>

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

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

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

可能的执行结果:

xy可能的执行顺序
12ra_c1
14ra_c2
34ra_c3

一致性 memory_order_seq_cst

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

#include <atomic>
#include <iostream>
#include <syncstream>  // c++20
#include <thread>

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

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

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

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

可能的执行步骤

xy可能的执行顺序
34sc_c1
32sc_c2
14在这里插入图片描述

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


6. 内存序的权衡

  • 性能优先: relaxed 内存序提供最高性能, 但不保证顺序和可见性.
  • 正确性优先: acquire/release 是实际编程中常用的同步机制.
  • 简单实现: seq_cst 是默认选择, 但代价是性能下降.

总结

  • C++ 内存序是原子操作的关键组成部分, 用于平衡性能和同步需求.
  • 默认使用 memory_order_seq_cst, 在性能敏感场景中使用 relaxedacquire/release.
  • 结合实际多线程需求合理选择内存序, 有助于写出高效且安全的代码.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

arong-xu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值