C++ 内存模型
外观
简介
C++内存模型是C++标准中定义的一套规则,用于描述多线程环境下程序如何访问内存。它规定了线程间共享数据的可见性、原子性以及操作顺序,是理解多线程编程中数据竞争、同步机制(如互斥锁、原子操作)的基础。C++11首次正式引入内存模型,解决了此前标准中多线程行为的未定义问题。
内存模型的核心目标是:
- 定义哪些操作是原子的(不可分割)。
- 规定线程间数据修改的可见性(何时对其他线程可见)。
- 控制指令的执行顺序(编译器/CPU优化的限制)。
关键概念
内存顺序(Memory Order)
C++提供了6种内存顺序,定义在std::memory_order
枚举中,用于控制原子操作的同步行为:
内存顺序 | 描述 |
---|---|
memory_order_relaxed |
无同步要求,仅保证原子性。 |
memory_order_consume |
依赖该原子变量的后续操作必须在其后执行(罕见使用)。 |
memory_order_acquire |
当前线程中后续的读操作必须在该原子操作之后执行。 |
memory_order_release |
当前线程中之前的写操作必须在该原子操作之前完成。 |
memory_order_acq_rel |
结合acquire和release,适用于读-改-写操作。 |
memory_order_seq_cst |
默认顺序,保证全局一致性(最强约束)。 |
原子操作
原子操作是不可中断的操作,通常通过std::atomic
类型实现。以下示例展示原子变量的使用:
#include <atomic>
#include <thread>
#include <iostream>
std::atomic<int> counter(0);
void increment() {
for (int i = 0; i < 1000; ++i) {
counter.fetch_add(1, std::memory_order_relaxed); // 原子递增
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Counter: " << counter << std::endl; // 输出2000
}
数据竞争与同步
未正确同步的共享数据访问会导致数据竞争(Data Race),引发未定义行为。例如:
int unsafe_counter = 0;
void unsafe_increment() {
for (int i = 0; i < 1000; ++i) {
++unsafe_counter; // 非原子操作,可能丢失更新
}
}
内存模型的实际应用
案例1:自旋锁实现
利用std::atomic_flag
和memory_order_acquire/release
实现高效自旋锁:
#include <atomic>
class SpinLock {
std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
void lock() {
while (flag.test_and_set(std::memory_order_acquire)); // 获取锁
}
void unlock() {
flag.clear(std::memory_order_release); // 释放锁
}
};
案例2:单例模式的双重检查锁
避免不必要的锁竞争,同时保证线程安全:
#include <atomic>
#include <mutex>
class Singleton {
static std::atomic<Singleton*> instance;
static std::mutex mtx;
Singleton() {}
public:
static Singleton* getInstance() {
Singleton* tmp = instance.load(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
tmp = instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton();
instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}
};
内存顺序的可视化
以下Mermaid图展示不同内存顺序的约束关系:
数学表达
C++内存模型的顺序一致性(Sequential Consistency)可形式化为:
总结
- 使用
std::atomic
避免数据竞争。 - 根据场景选择合适的内存顺序(例如
seq_cst
为默认安全选项)。 - 理解“Happens-Before”关系是分析多线程程序的关键。
模板:Stub 注:本文仅覆盖基础内容,高级主题如内存屏障(Fence)需进一步学习。