C++內存序的操作方法
更新時間:2025年11月04日 09:29:54 作者:2301_80355452
在C++中,內存序(Memory Order)是一個非常重要的概念,特別是在多線程編程中,本文通過實例代碼介紹C++內存序的相關知識,感興趣的朋友一起看看吧
1.為什么需要內存序?
問題的根源:現(xiàn)代CPU的亂序執(zhí)行
// 從程序員角度看是順序執(zhí)行
int x = 0, y = 0;
void thread1() {
x = 1; // 步驟1
y = 1; // 步驟2
}
void thread2() {
if (y == 1) {
assert(x == 1); // 可能失??!
}
}實際執(zhí)行可能:
- CPU為了優(yōu)化,可能先執(zhí)行
y = 1,后執(zhí)行x = 1 - 編譯器也可能重排指令
- 導致線程2看到
y == 1但x == 0
2.六種內存序詳解
內存序概覽表
| 內存序 | 作用 | 使用場景 | 性能 |
|---|---|---|---|
relaxed | 只保證原子性 | 計數(shù)器、統(tǒng)計 | 最快 |
consume | 數(shù)據(jù)依賴排序 | 指針發(fā)布 | 較快 |
acquire | 加載屏障 | 讀側同步 | 中等 |
release | 存儲屏障 | 寫側同步 | 中等 |
acq_rel | 加載+存儲屏障 | CAS操作 | 較慢 |
seq_cst | 全序屏障 | 默認,最安全 | 最慢 |
3.逐層深入理解
3.1memory_order_relaxed- 最寬松
#include <atomic>
#include <thread>
#include <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(tǒng)=1但x=0
int r2 = x.load(std::memory_order_relaxed);
std::cout << "y=" << r1 << ", x=" << r2 << std::endl;
});
t1.join(); t2.join();
}適用場景:
// 計數(shù)器 - 順序不重要,只要原子就行
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. 準備數(shù)據(jù)
flag.store(true, std::memory_order_release); // 2. 發(fā)布標志(保證1在2之前)
}
void consumer() {
while (!flag.load(std::memory_order_acquire)) { // 3. 獲取標志(保證4在3之后)
// 等待
}
assert(data == 42); // 4. 讀取數(shù)據(jù)(這里一定能看到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 thread_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;
delete 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; // 需要acquire保證看到完整對象狀態(tài)
delete ref_count;
// 同時,在delete之前的所有對象修改
// 必須對其他線程可見 (release語義)
}
}
};5.內存序的層次關系
從弱到強的約束:
relaxed (最弱) ↓ consume ↓ acquire/release ↓ acq_rel ↓ seq_cst (最強)
6.實際編程建議
6.1什么時候用什么內存序
// 情況1:簡單計數(shù)器
std::atomic<int> counter{0};
counter.fetch_add(1, std::memory_order_relaxed); // ?
// 情況2:標志位同步
std::atomic<bool> ready{false};
int data;
// 生產(chǎn)者
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失敗,重試
}
// 鏈接節(jié)點
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順序下,可能發(fā)生:
// 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,確保正確性 - 性能優(yōu)化:在理解的基礎上使用更寬松的內存序
- shared_ptr場景:
acq_rel是正確的選擇 - 標志同步:
acquire/release是最佳選擇
您的shared_ptr實現(xiàn)中使用的 memory_order_acq_rel 是非常合適的,它確保了在釋放資源時能夠看到對象的完整狀態(tài),同時也保證了對象修改對其他線程的可見性。
到此這篇關于C++內存序的操作方法的文章就介紹到這了,更多相關C++內存序內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
詳解C++編程中向函數(shù)傳遞引用參數(shù)的用法
這篇文章主要介紹了詳解C++編程中向函數(shù)傳遞引用參數(shù)的用法,包括使函數(shù)返回引用類型以及對指針的引用,需要的朋友可以參考下2016-01-01

