C++內(nèi)存序的操作方法
1.為什么需要內(nèi)存序?
問題的根源:現(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 - 編譯器也可能重排指令
- 導(dǎo)致線程2看到
y == 1但x == 0
2.六種內(nèi)存序詳解
內(nèi)存序概覽表
| 內(nèi)存序 | 作用 | 使用場景 | 性能 |
|---|---|---|---|
relaxed | 只保證原子性 | 計數(shù)器、統(tǒng)計 | 最快 |
consume | 數(shù)據(jù)依賴排序 | 指針發(fā)布 | 較快 |
acquire | 加載屏障 | 讀側(cè)同步 | 中等 |
release | 存儲屏障 | 寫側(cè)同步 | 中等 |
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,設(shè)置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中的實際應(yīng)用
您代碼中的內(nèi)存序分析:
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.內(nèi)存序的層次關(guān)系
從弱到強的約束:
relaxed (最弱) ↓ consume ↓ acquire/release ↓ acq_rel ↓ seq_cst (最強)
6.實際編程建議
6.1什么時候用什么內(nèi)存序
// 情況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:復(fù)雜的同步邏輯(不確定時)
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.測試和驗證
內(nèi)存序錯誤的檢測:
#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已設(shè)置) 但 r2 == 0 (x還未設(shè)置)
std::cout << "Result: r1=" << r1 << ", r2=" << r2 << std::endl;
}8.總結(jié)
關(guān)鍵要點:
relaxed:只保證原子性,不保證順序acquire/release:配對使用,建立線程間happens-before關(guān)系acq_rel:用于讀-修改-寫操作,兼具兩者特性seq_cst:全局順序,最安全但性能最差
使用建議:
- 初學(xué)者:先用
seq_cst,確保正確性 - 性能優(yōu)化:在理解的基礎(chǔ)上使用更寬松的內(nèi)存序
- shared_ptr場景:
acq_rel是正確的選擇 - 標志同步:
acquire/release是最佳選擇
您的shared_ptr實現(xiàn)中使用的 memory_order_acq_rel 是非常合適的,它確保了在釋放資源時能夠看到對象的完整狀態(tài),同時也保證了對象修改對其他線程的可見性。
到此這篇關(guān)于C++內(nèi)存序的操作方法的文章就介紹到這了,更多相關(guān)C++內(nèi)存序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
記逆向小白的第一次vbsedit 9爆破及內(nèi)存補丁制作過程
這篇文章主要介紹了記逆向小白的第一次vbsedit 9爆破及內(nèi)存補丁制作過程,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04
C++11中初始化列表initializer lists的使用方法
C++11引入了初始化列表來初始化變量和對象,自定義類型,如果想用初始化列表就要包含initializer_list頭文件2021-09-09
C++深入詳解單例模式與特殊類設(shè)計的實現(xiàn)
這篇文章主要為大家詳細介紹了C++單例模式和特殊類的設(shè)計,單例模式這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-06-06
C++中sprintf使用的方法與printf的區(qū)別分析
這篇文章主要介紹了C++中sprintf使用的方法與printf的區(qū)別,實例分析了sprintf與printf的具體用法及相關(guān)注意事項,具有一定參考借鑒價值,需要的朋友可以參考下2015-01-01

