目录
- 1.为什么需要内存序?
- 问题的根源:现代CPU的乱序执行
- 2.六种内存序详解
- 内存序概览表
- 3.逐层深入理解
- 3.1memory_order_relaxed- 最宽松
- 3.2memory_order_acquire和memory_order_release- 配对使用
- 3.3memory_order_acq_rel- 读改写操作
- 3.4memory_order_seq_cst- 顺序一致性(默认)
- 4.在shared_ptr中的实际应用
- 您代码中的内存序分析:
- 5.内存序的层次关系
- 从弱到强的约束:
- 6.实际编程建议
- 6.1什么时候用什么内存序
- 6.2实用示例:无锁队列
- 7.测试和验证
- 内存序错误的检测:
- 8.总结
- 关键要点:
- 使用建议:
1.为什么需要内存序?
问题的根源:现代CPU的乱序执行
// 从程序员角度看是顺序执行
int x = 0, y = 0;
void thread1() {
x = 1; // 步骤1
y = 1; // 步骤2
}
void thread2() {
if (y == 1) {
assert(x == 1); // 可能失败!
}
}
实际执行可能:
- CPU为了优化,可能先执行
y = 1,后执行x = 1 - 编译器也可能重排指令
- 导致线程2看到
y == 1但x == 0
2.六种内存序详解
内存序概览表
| 内存序 | 作用 | 使用场景 | 性能 |
|---|---|---|---|
relaxed | 只保证原子性 | 计数器、统计 | 最快 |
consume | 数据依赖排序 | 指针发布 | 较快 |
acquire | 加载屏障 | 读侧同步 | 中等 |
release | 存储屏障 | 写侧同步 | 中等 |
acq_rel | 加载+存储屏障 | CAS操作 | 较慢 |
seq_cst | 全序屏障 | 默认,最安全 | 最慢 |
3.逐层深入理解
3.1memory_order_relaxed- 最宽松
#include <atomic>
#include <thread>
#inwww.devze.comclude <IOStream>
std::atomic<int> x(0), y(0);
void relaxed_example() {
// 线程1
std::thread t1([](){
x.store(1, std::memory_order_relaxed); // 可能重排
y.store(1, std::memory_order_relaxed); // 可能重排
});
// 线程2
std::thread t2([](){
int r1 = y.load(std::memory_order_relaxed); // 可能看到y=1但x=0
int r2 = x.load(std::memory_order_relaxed);
std::cout << "y=" << r1 << ", x=" << r2 << std::endl;
});
t1.join(); t2.join();
}
适用场景:
// 计数器 - 顺序不重要,只要原子就行
std::atomic<int> counter(0);
void increment() {
counter.fetch_add(1, std::memory_order_relaxed);
}
3.2memory_order_acquire和memory_order_release- 配对使用
#include <atomic>
#include <thread>
#include <cassert>
std::atomic<bool> flag{false};
int data = 0;
void producer() {
data = 42; // 1. 准备数据
flag.store(true, std::memory_order_release); // 2. 发布标志(保证1在2之前)
}
void consumer() {
while (!flag.load(std::memory_order_acquire)) { // 3. 获取标志(保证4在3之后)
// 等待
}
assert(data == 42); // 4. 读取数据(这里一定能看到42!)
}
屏障效果图示:
线程A (Producer) 线程B (Consumer)
data = 42
↓ (release屏障)
flag = true
─────→ while(!flag)
↓ (acquire屏障)
assert(data==42) ✓
3.3memory_order_acq_rel- 读改写操作
class SpinLock {
std::atomic<bool> locked{false};
public:
void lock() {
// 期望locked=false,设置locked=true
// 需要同时保证acquire和release语义
while (locked.exchange(true, std::memory_order_acq_rel)) {
// 自旋等待
}
}
void unlock() {
locked.store(false, std::memory_order_release);
}
};
为什么需要acq_rel:
std::atomic<int> shared{0};
int normal_data = 0;
void tcwZQPahread_work() {
// 修改前操作
normal_data = 100;
// CAS操作:既有读又有写
int expected = 0;
if (shared.compare_exchange_strong(expected, 1,
std::memory_order_acq_rel)) {
// 成功:相当于acquire,能看到之前的修改
// 同时:相当于release,保证之前的修改对其他线程可见
}
}
3.4memory_order_seq_cst- 顺序一致性(默认)
std::atomic<int> x{0}, y{0};
void sequential_example() {
std::thread t1([](){
x.store(1, std::memory_order_seq_cst); // 1
y.store(1, std::memory_order_seq_cst); // 2
});
std::thread t2([](){
int r1 = y.load(std::memory_order_seq_cst); // 3
int r2 = x.load(std::memory_order_seq_cst); // 4
// 所有线程看到相同的操作顺序
// 可能的顺序:1→2→3→4 或 1→3→2→4 等
// 但所有线程对顺序的理解一致
});
t1.join(); t2.join();
}
4.在shared_ptr中的实际应用
您代码中的内存序分析:
void release() {
if (ref_count && ref_count->fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete ptr;
dphpelete ref_count;
}
}
// 为什么用 memory_order_acq_rel?
详细解释:
class shared_ptr {
void release() {
// fetch_sub是读-修改-写操作:
// 1. 读取当前值 (需要acquire语义)
// 2. 减去1
// 3. 写回新值 (需要release语义)
// 使用acq_rel确保:
if (ref_count &&
ref_count->fetch_sub(1, std::memory_order_acq_rel) == 1) {
// ↓ 这个delete操作必须看到ptr的所有修改
delete ptr; cwZQPa // 需要acquire保证看到完整对象状态
delete ref_count;
// 同时,在delete之前的所有对象修改
// 必须对其他线程可见 (release语义)
}
}
};
5.内存序的层次关系
从弱到强的约束:
relaxed (最弱) ↓ consume ↓ acquire/release ↓ acq_rel ↓ seq_cst (最强)
6.实际编程建议
6.1什么时候用什么内存序
// 情况1:简单计数器
std::atomic<int> counter{0};
counter.fetch_add(1, std::memory_order_relaxed); // ✅
// 情况2:标志位同步
std::atomic<bool> ready{false};
int data;
// 生产者
data = compute_data();
ready.store(true, std::memory_order_release); // ✅
/编程客栈/ 消费者
while (!ready.load(std::memory_order_acquire)) {} // ✅
use_data(data);
// 情况3:复杂的同步逻辑(不确定时)
std::atomic<int> state{0};
state.compare_exchange_strong(old_val, new_val,
std::memory_order_seq_cst); // ✅ 安全第一
6.2实用示例:无锁队列
template<typename T>
class LockFreeQueue {
struct Node {
T data;
std::atomic<Node*> next;
};
std::atomic<Node*> head, tail;
public:
void push(const T& value) {
Node* new_node = new Node{value, nullptr};
Node* old_tail = tail.load(std::memory_order_acquire);
while (!tail.compare_exchange_weak(old_tail, new_node,
std::memory_order_acq_rel,
std::memory_order_acquire)) {
// CAS失败,重试
}
// 链接节点
old_tail->next.store(new_node, std::memory_order_release);
}
};
7.测试和验证
内存序错误的检测:
#include <atomic>
#include <thread>
#include <cassert>
std::atomic<int> x{0}, y{0};
int r1, r2;
void memory_order_test() {
std::thread t1([](){
x.store(1, std::memory_order_relaxed);
y.store(1, std::memory_order_relaxed);
});
std::thread t2([](){
r1 = y.load(std::memory_order_relaxed);
r2 = x.load(std::memory_order_relaxed);
});
t1.join(); t2.join();
// 在relaxed顺序下,可能发生:
// r1 == 1 (y已设置) 但 r2 == 0 (x还未设置)
std::cout << "Result: r1=" << r1 << ", r2=" << r2 << std::endl;
}
8.总结
关键要点:
relaxed:只保证原子性,不保证顺序acquire/release:配对使用,建立线程间happens-before关系acq_rel:用于读-修改-写操作,兼具两者特性seq_cst:全局顺序,最安全但性能最差
使用建议:
- 初学者:先用
seq_cst,确保正确性 - 性能优化:在理解的基础上使用更宽松的内存序
- shared_ptr场景:
acq_rel是正确的选择 - 标志同步:
acquire/release是最佳选择
您的shared_ptr实现中使用的 memory_order_acq_rel 是非常合适的,它确保了在释放资源时能够看到对象的完整状态,同时也保证了对象修改对其他线程的可见性。
到此这篇关于C++内存序的操作方法的文章就介绍到这了,更多相关C++内存序内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
加载中,请稍侯......
精彩评论