文章目录
- [C++ std::memory_order 完全解析](#C++ std::memory_order 完全解析)
-
- 一、语法与版本变迁
-
- [C++11 ~ C++17:普通枚举](#C++11 ~ C++17:普通枚举)
- [C++20 起:强类型枚举(更类型安全)](#C++20 起:强类型枚举(更类型安全))
- 关键废弃说明
- 二、核心概念(理解内存序的基础)
-
- [1. 序列前(Sequenced-before)](#1. 序列前(Sequenced-before))
- [2. 发生前(Happens-before)](#2. 发生前(Happens-before))
- [3. 同步于(Synchronizes-with)](#3. 同步于(Synchronizes-with))
- [4. 修改顺序(Modification order)](#4. 修改顺序(Modification order))
- [5. 释放序列(Release sequence)](#5. 释放序列(Release sequence))
- 三、6种内存序的详细说明与适用场景
-
- [1. memory_order_relaxed(宽松序)](#1. memory_order_relaxed(宽松序))
- [2. memory_order_consume(消费序,C++26废弃)](#2. memory_order_consume(消费序,C++26废弃))
- [3. memory_order_acquire(获取序)](#3. memory_order_acquire(获取序))
- [4. memory_order_release(释放序)](#4. memory_order_release(释放序))
- [5. acquire-release 组合(最常用的跨线程同步模式)](#5. acquire-release 组合(最常用的跨线程同步模式))
- [6. memory_order_acq_rel(获取-释放序)](#6. memory_order_acq_rel(获取-释放序))
- [7. memory_order_seq_cst(顺序一致序)](#7. memory_order_seq_cst(顺序一致序))
- 四、与volatile的关键区别(易混淆点)
- 五、性能与使用原则
-
- [1. 性能排序(从快到慢)](#1. 性能排序(从快到慢))
- [2. 核心使用原则](#2. 核心使用原则)
- 六、总结
C++ std::memory_order 完全解析
std::memory_order 是C++11起引入的原子操作内存序枚举,定义于<atomic>头文件,用于约束原子操作周围的内存访问顺序(包括普通非原子内存访问),解决多线程环境下因编译器重排、CPU乱序执行导致的内存可见性和指令重排问题,是C++内存模型的核心组成部分。
原子操作的默认内存序为memory_order_seq_cst(顺序一致),虽保证最强的内存一致性,但会带来性能损耗;通过显式指定std::memory_order,可在正确性 和性能之间做精细化权衡。
一、语法与版本变迁
C++11 ~ C++17:普通枚举
cpp
enum memory_order {
memory_order_relaxed, // 宽松序
memory_order_consume, // 消费序(C++26已废弃)
memory_order_acquire, // 获取序
memory_order_release, // 释放序
memory_order_acq_rel, // 获取-释放序
memory_order_seq_cst // 顺序一致序
};
C++20 起:强类型枚举(更类型安全)
同时提供兼容旧代码的全局常量:
cpp
enum class memory_order : /* 未指定底层类型 */ {
relaxed, consume, acquire, release, acq_rel, seq_cst
};
// 全局常量,兼容旧代码
inline constexpr memory_order memory_order_relaxed = memory_order::relaxed;
inline constexpr memory_order memory_order_consume = memory_order::consume;
inline constexpr memory_order memory_order_acquire = memory_order::acquire;
inline constexpr memory_order memory_order_release = memory_order::release;
inline constexpr memory_order memory_order_acq_rel = memory_order::acq_rel;
inline constexpr memory_order memory_order_seq_cst = memory_order::seq_cst;
关键废弃说明
memory_order_consume(消费序)在C++26中正式废弃 ,C++17起已建议避免使用;目前主流编译器均将消费序提升为获取序(acquire)处理,C++26后其行为完全等价于acquire。
二、核心概念(理解内存序的基础)
内存序的本质是通过约束指令重排 和建立内存可见性,在多线程间建立可靠的执行顺序关系,核心依赖以下基础概念:
1. 序列前(Sequenced-before)
同一线程内 的指令执行顺序:若操作A序列前于操作B,则A必然在B之前执行,编译器和CPU不会重排这两个操作(单线程内的天然顺序)。
2. 发生前(Happens-before)
跨线程 的核心顺序关系,是避免数据竞争的关键:若操作A发生前 于操作B,则A的所有内存写操作对B的读操作可见且有序。
- 单线程内:
序列前→ 隐含发生前; - 跨线程:需通过原子操作的内存序显式建立
发生前关系。
3. 同步于(Synchronizes-with)
跨线程建立发生前关系的直接手段 :若线程A的原子释放操作(release)写入的值,被线程B的原子获取操作(acquire)读取到,则A的释放操作同步于B的获取操作,进而推导出"A的所有序列前操作发生前于B的所有序列后操作"。
4. 修改顺序(Modification order)
对同一个原子变量 的所有修改操作,在所有线程看来都遵循唯一的全序(即所有线程看到该原子变量的修改顺序一致),这是原子操作的基本保证。
5. 释放序列(Release sequence)
以某个原子释放操作为起点,该原子变量后续的连续修改序列(包括同一线程的写操作、任意线程的读-改-写操作),后续的获取操作只要读取到释放序列中任意一个值,即可建立同步关系。
三、6种内存序的详细说明与适用场景
6种内存序按约束强度从弱到强 排序:relaxed < consume(废弃) < acquire/release < acq_rel < seq_cst,约束越弱性能越好,约束越强一致性越可靠。
1. memory_order_relaxed(宽松序)
核心特性
- 仅保证操作本身的原子性 和原子变量的修改顺序一致性;
- 无任何内存序约束:不阻止编译器/CPU重排该操作与其他内存访问(包括普通变量、其他原子变量);
- 无跨线程同步 :不建立任何
发生前关系,其他线程看到的操作顺序可能混乱。
适用场景
仅需要原子性 ,无需内存可见性和顺序约束的场景,典型如:
- 无依赖的计数器(如
std::shared_ptr的引用计数自增,自减需acquire/release); - 统计事件发生次数、简单的状态标记(无后续依赖操作)。
代码示例
cpp
#include <atomic>
#include <thread>
#include <vector>
#include <iostream>
std::atomic<int> cnt = {0};
void increment() {
for (int i = 0; i < 1000; ++i) {
// 仅需原子自增,无需顺序约束
cnt.fetch_add(1, std::memory_order_relaxed);
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(increment);
}
for (auto& t : threads) t.join();
std::cout << "最终计数:" << cnt << std::endl; // 必然输出10000
return 0;
}
2. memory_order_consume(消费序,C++26废弃)
核心特性
- 仅对依赖于该原子操作结果 的操作生效:当前线程中,所有数据依赖于该原子加载结果的操作,不能被重排到该加载操作之前;
- 轻量级同步:仅保证"释放线程中,数据依赖于该原子变量的写操作"对"消费线程中,数据依赖于该原子加载结果的操作"可见;
- 主流编译器已将其提升为acquire,C++26后完全废弃。
适用场景(原设计)
指针介导的发布-订阅模式(如RCU机制),仅需保证指针指向的数据可见,无需保证所有内存写可见。
代码示例(仅作历史参考,建议用acquire替代)
cpp
#include <atomic>
#include <string>
#include <thread>
#include <cassert>
std::atomic<std::string*> ptr;
int non_dep_data = 0; // 与ptr无数据依赖
void producer() {
std::string* p = new std::string("Hello");
non_dep_data = 42;
ptr.store(p, std::memory_order_release); // 释放序
}
void consumer() {
std::string* p2;
while (!(p2 = ptr.load(std::memory_order_consume))) ; // 消费序
assert(*p2 == "Hello"); // 必然成立:*p2依赖于ptr的加载结果
// 可能不成立:non_dep_data与ptr无数据依赖,无可见性保证
// assert(non_dep_data == 42);
}
int main() {
std::thread t1(producer), t2(consumer);
t1.join(); t2.join();
delete ptr.load();
return 0;
}
3. memory_order_acquire(获取序)
核心特性
- 仅适用于原子加载操作(load);
- 约束:当前线程中,所有内存读/写操作 不能被重排到该获取操作之前;
- 同步:若读取到某个释放操作(release)写入的值,则该释放操作同步于当前获取操作,进而获得释放线程所有前置操作的内存可见性。
适用场景
消费者线程获取共享资源的同步点,典型如:
- 加载原子标记,判断生产者是否已完成资源初始化;
- 互斥锁的
lock()操作(天然具备acquire语义)。
4. memory_order_release(释放序)
核心特性
- 仅适用于原子存储操作(store);
- 约束:当前线程中,所有内存读/写操作 不能被重排到该释放操作之后;
- 同步:若写入的值被某个获取操作(acquire)读取到,则当前释放操作同步于该获取操作,进而让当前线程所有前置操作对消费者线程可见。
适用场景
生产者线程发布共享资源的同步点,典型如:
- 存储原子标记,通知消费者资源已初始化完成;
- 互斥锁的
unlock()操作(天然具备release语义)。
5. acquire-release 组合(最常用的跨线程同步模式)
acquire和release必须配对使用 ,是C++多线程中最常用、性能与一致性平衡最好 的同步模式,核心作用是建立跨线程的发生前关系,保证共享资源的可见性。
核心保证
若:
- 线程A对原子变量M执行
release存储; - 线程B对原子变量M执行
acquire加载,且读取到A写入的值;
则:线程A中所有序列前于release的操作,发生前于线程B中所有序列后于acquire的操作。
代码示例(经典的生产者-消费者同步)
cpp
#include <atomic>
#include <string>
#include <thread>
#include <cassert>
std::atomic<std::string*> data_ptr; // 原子指针,作为同步点
int shared_data = 0; // 普通共享变量
// 生产者线程:初始化资源,然后释放
void producer() {
std::string* p = new std::string("共享资源");
shared_data = 42; // 先初始化普通共享变量
// 释放序:保证上面的写操作不被重排到之后,且对获取方可见
data_ptr.store(p, std::memory_order_release);
}
// 消费者线程:获取同步点,然后访问资源
void consumer() {
std::string* p2;
// 获取序:循环等待生产者的释放操作
while (!(p2 = data_ptr.load(std::memory_order_acquire))) ;
// 必然成立:release-acquire保证了生产者所有前置操作的可见性
assert(*p2 == "共享资源");
assert(shared_data == 42);
delete p2;
}
int main() {
std::thread t1(producer), t2(consumer);
t1.join(); t2.join();
return 0;
}
6. memory_order_acq_rel(获取-释放序)
核心特性
- 仅适用于原子读-改-写操作 (RMW:read-modify-write,如
fetch_add、compare_exchange_strong、swap等); - 兼具
acquire和release的双重语义:- 作为加载 时,具备
acquire语义(当前线程后续操作不能重排到其前); - 作为存储 时,具备
release语义(当前线程前置操作不能重排到其后);
- 作为加载 时,具备
- 可同时与前序的释放操作、后序的获取操作建立同步关系。
适用场景
需要同时获取前置操作可见性、并发布自身操作可见性的RMW操作,典型如:
- 原子计数器的自减(需保证减操作前的读可见,减操作后的写对后续操作可见);
- 自旋锁的加锁/解锁、原子变量的交换操作。
代码示例(三线程的传递性同步)
cpp
#include <atomic>
#include <vector>
#include <thread>
#include <cassert>
std::vector<int> shared_vec;
std::atomic<int> flag = {0};
// 线程1:生产资源,释放flag(release)
void thread1() {
shared_vec.push_back(42);
flag.store(1, std::memory_order_release);
}
// 线程2:RMW操作,兼具acquire和release(acq_rel)
void thread2() {
int expected = 1;
// 读-改-写:acq_rel保证读取到flag=1时(acquire)看到thread1的操作,
// 写入flag=2时(release)让thread3看到自身操作
while (!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel)) {
expected = 1;
}
}
// 线程3:获取flag(acquire),访问资源
void thread3() {
while (flag.load(std::memory_order_acquire) < 2) ;
assert(shared_vec.at(0) == 42); // 必然成立:传递性同步
}
int main() {
std::thread t1(thread1), t2(thread2), t3(thread3);
t1.join(); t2.join(); t3.join();
return 0;
}
7. memory_order_seq_cst(顺序一致序)
核心特性
- 最强的内存序 ,是所有原子操作的默认值;
- 兼具
acquire/release/acq_rel的所有语义; - 额外保证:所有线程看到的所有seq_cst原子操作,遵循全局唯一的全序(即所有线程观察到的原子操作执行顺序完全一致);
- 性能损耗最大:在多核心CPU上,通常需要全内存栅栏(full memory fence) 指令,强制所有核心的内存缓存同步。
适用场景
需要全局统一操作顺序的场景,典型如:
- 多生产者-多消费者模型,要求所有消费者看到的生产者操作顺序完全一致;
- 对操作顺序有严格全局要求的场景(如分布式状态同步、全局事件计数)。
代码示例(必须保证全局顺序的场景)
cpp
#include <atomic>
#include <thread>
#include <cassert>
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
// 写x(seq_cst)
void write_x() { x.store(true, std::memory_order_seq_cst); }
// 写y(seq_cst)
void write_y() { y.store(true, std::memory_order_seq_cst); }
// 读x再读y,满足则z++
void read_x_then_y() {
while (!x.load(std::memory_order_seq_cst)) ;
if (y.load(std::memory_order_seq_cst)) z++;
}
// 读y再读x,满足则z++
void read_y_then_x() {
while (!y.load(std::memory_order_seq_cst)) ;
if (x.load(std::memory_order_seq_cst)) z++;
}
int main() {
std::thread a(write_x), b(write_y), c(read_x_then_y), d(read_y_then_x);
a.join(); b.join(); c.join(); d.join();
assert(z.load() != 0); // 必然成立:seq_cst保证全局全序,不会出现x/y互相不可见的情况
return 0;
}
若替换为acquire/release,则assert可能触发------因为不同线程可能看到x和y的写入顺序相反,导致z始终为0。
四、与volatile的关键区别(易混淆点)
很多开发者会混淆std::atomic+内存序与volatile,二者无任何替代关系,核心区别如下:
| 特性 | std::atomic + memory_order | volatile |
|---|---|---|
| 原子性 | 保证操作原子性,无数据竞争 | 不保证原子性,并发读写是数据竞争 |
| 跨线程同步 | 可建立happens-before关系,保证可见性 | 无跨线程同步,仅单线程内不重排 |
| 内存序约束 | 可精细化控制(relaxed/seq_cst等) | 仅单线程内不重排volatile访问 |
| 非volatile操作重排 | 按指定内存序约束,阻止跨线程重排 | 可自由重排volatile周围的普通操作 |
| 适用场景 | 多线程共享数据的同步与访问 | 单线程内与硬件/信号处理的交互 |
注意 :Visual Studio有非标准扩展------默认下volatile具备acquire/release语义,可用于简单同步,但这不是C++标准 ;使用/volatile:iso编译选项可恢复标准行为。
五、性能与使用原则
1. 性能排序(从快到慢)
relaxed > acquire/release > acq_rel > seq_cst
- 强有序CPU(x86/64、SPARC TSO):acquire/release几乎无性能损耗(仅阻止编译器重排,无需CPU栅栏指令);
- 弱有序CPU(ARM、PowerPC、Itanium):acquire/release需要轻量级栅栏,seq_cst需要全栅栏,性能差异显著。
2. 核心使用原则
- 最小约束原则 :在保证程序正确性的前提下,尽可能使用最弱的内存序,以获得最佳性能;
- 配对使用:acquire必须与release配对,单独使用无同步意义;
- 避免滥用seq_cst:仅当需要全局全序时使用,否则优先acquire/release;
- 废弃consume:C++26已废弃,直接使用acquire替代,无性能损失且更可靠;
- RMW操作用acq_rel:读-改-写操作需要同时获取和释放语义时,使用acq_rel而非单独的acquire/release。
六、总结
std::memory_order是C++内存模型的核心,用于约束原子操作的内存访问顺序,解决多线程的指令重排 和内存可见性问题;- 6种内存序按约束强度排序:
relaxed<consume(废弃) <acquire/release<acq_rel<seq_cst,约束越弱性能越好; - acquire-release是最常用的同步模式,配对使用可建立跨线程的happens-before关系,保证共享资源可见性;
seq_cst是默认内存序,保证全局唯一全序,但性能损耗最大,非必要不使用;volatile不能替代原子操作,二者设计目标完全不同,多线程同步必须使用std::atomic+内存序;- 版本注意:C++20将memory_order改为强类型枚举,C++26废弃consume,建议代码中直接使用acquire替代consume以保证兼容性。
核心口诀:轻量原子用relaxed,跨线程同步用acquire-release,RMW操作用acq_rel,全局全序用seq_cst。