文章目錄
- 1. 什么是原子操作
- 2. 原子操作的特點
- 3. 原子操作的底層原理
- 4. 內存序
- 內存屏障
- 5. 原子操作和互斥鎖的對比
- 6. 常用的原子操作
- 7. 相關問題討論
參考:
- C++ atomic 原子操作_c++ 原子操作-CSDN博客
- DeepSeek
1. 什么是原子操作
原子操作(Atomic Operations)是不可分割的操作,保證在多線程環境中執行時不會被中斷,避免數據競爭(Data Race)。即:原子操作要么完全執行,要么完全不執行,不會在執行期間被其他線程操作干擾。
C++11引入<atomic>
頭文件,提供std::atomic<T>
模板類,支持對基本類型(如int
、bool
、指針)的原子操作。
2. 原子操作的特點
- 無鎖(Lock-Free): 原子操作通常由硬件指令(如CAS,LL/SC)實現,無需顯式加鎖。
- 線程安全: 保證對變量的讀寫操作在多線程環境下的正確性。
- 輕量級: 適合簡單操作(如計數器、標志位),性能通常高于互斥鎖。
示例:
#include <atomic>
std::atomic<int> counter(0);void increment() {counter.fetch_add(1, std::memory_order_relaxed); // 原子遞增
}
3. 原子操作的底層原理
原子操作的實現依賴于硬件指令,例如:
-
CAS(Compare-And-Swap):通過比較內存中的值與預期值,若匹配則更新為新值。
bool compare_exchange_strong(T& expected, T desired) {if (current == expected) {current = desired;return true;} else {expected = current;return false;} }
-
LL/SC(Load-Linked/Store-Conditional):在特定地址上標記“監視”,若未被其他線程修改則寫入。
這些指令保證操作的原子性,但不同硬件(x86、ARM)的實現差異較大。例如:
- x86:通過
LOCK
前綴指令(如LOCK XCHG
)實現原子操作。 - ARM:依賴LL/SC指令實現無鎖原子操作。
原子操作的局限性
- ABA問題:線程A讀取值
A
,線程B將值改為B
后又改回A
,導致A的CAS誤判無變化。
解決方案:使用帶版本號的原子變量(如std::atomic<std::pair<T, uint64_t>>
)。 - 適用范圍:僅適用于基本類型(如
int
、bool
、指針)。復雜類型需使用std::atomic_flag
或互斥鎖。
4. 內存序
內存序(Memory Order)定義了原子操作之間的可見性和順序約束,確保多線程環境下的內存訪問符合預期。以下問題可能導致指令順序變化:
- 編譯器重排:編譯器優化可能導致指令順序變化。
- CPU重排:現代CPU的亂序執行機制(如Store Buffer、Invalidate Queue)可能打亂指令順序。
- 緩存一致性:不同核心的緩存狀態可能導致內存視圖不一致。
C++提供6種內存序,分為三類:
內存序 | 描述 |
---|---|
順序一致性(Sequential Consistency) | |
memory_order_seq_cst | 最嚴格,保證全局順序一致,性能較低。默認選項。 |
獲取-釋放(Acquire-Release) | |
memory_order_acquire | 保證后續讀操作不會重排到該操作之前(用于“獲取”同步)。 |
memory_order_release | 保證前面的寫操作不會重排到該操作之后(用于“釋放”同步)。 |
memory_order_acq_rel | 同時包含acquire和release語義(用于讀-改-寫操作)。 |
寬松(Relaxed) | |
memory_order_relaxed | 僅保證原子性,無順序約束(適用于計數器等無需同步的場景)。 |
memory_order_consume | 依賴順序(較弱的acquire,C++17后不推薦使用)。 |
典型場景:
生產者-消費者模型:
// 生產者線程
data = ...; // 生產數據
flag.store(true, std::memory_order_release); // 發布數據// 消費者線程
while (!flag.load(std::memory_order_acquire)); // 獲取數據
use_data(data); // 安全使用數據
內存屏障
1. 內存屏障的作用
內存屏障是硬件或編譯器級別的指令,用于強制限制內存操作的執行順序,分為兩類:
- 編譯器屏障:阻止編譯器重排指令(如
asm volatile("" ::: "memory")
)。 - 硬件屏障:阻止CPU重排內存訪問(如x86的
MFENCE
、ARM的DMB
)。
2. C++內存序與內存屏障的映射
-
std::atomic_thread_fence()
:顯式插入內存屏障。std::atomic_thread_fence(std::memory_order_acquire); // 插入讀屏障
-
隱式屏障:原子操作的內存序參數隱式插入屏障:
a.store(1, std::memory_order_release); // 隱式插入Store屏障
3. 內存屏障的實際案例
示例:無鎖隊列的入隊操作
// 生產者線程
Node* new_node = new Node(data);
new_node->next.store(head, std::memory_order_relaxed);
head.store(new_node, std::memory_order_release); // 插入寫屏障,確保new_node初始化完成后再更新head// 消費者線程
Node* local_head = head.load(std::memory_order_acquire); // 插入讀屏障,確保讀取到最新的head
if (local_head != nullptr) {// 安全操作local_head->next
}
5. 原子操作和互斥鎖的對比
特性 | 原子操作 | 互斥鎖(如std::mutex) |
---|---|---|
實現方式 | 硬件指令(如CAS)實現無鎖操作。 | 通過操作系統內核的鎖機制(可能涉及上下文切換)。 |
性能 | 低競爭時性能高,高競爭時可能自旋。 | 高競爭時可能更高效(線程休眠)。 |
適用場景 | 簡單操作(如計數器、標志位)。 | 復雜操作或需要保護多個變量/代碼塊。 |
內存序復雜性 | 需顯式指定內存序,易出錯。 | 隱式保證順序一致性,更簡單。 |
死鎖風險 | 無。 | 需避免死鎖(如加鎖順序不一致)。 |
ABA問題 | 可能發生(需配合版本號解決)。 | 無。 |
選擇建議:
- 使用原子操作:需要高性能的簡單操作,且能正確處理內存序。
- 使用互斥鎖:保護復雜邏輯或臨界區較長時,簡化代碼并減少錯誤。
6. 常用的原子操作
-
load
:讀取原子變量的值std::atomic<int> a(1); int value = a.load();
-
store
:將一個值存儲到原子變量中std::atomic<int> a(1); a.store(2); // a的值變為2
-
exchange
:將原子變量的值替換為另外一個值,并返回舊值常用來在并發環境下進行“交換”操作。
std::atomic<int> a(1); int old_value = a.exchange(2); // old_value為1,a的值變為2
-
compare_exchange_weak / compare_exchange_strong
:原子地進行條件交換操作。若當前值等于預期值,則交換新值,否則返回false
。weak
失敗后可能會重新嘗試,性能相對較高;strong
失敗后會返回false
并不再嘗試。std::atomic<int> a(1); int expected = 1; if (a.compare_exchange_weak(expected, 2)) {// 如果a的值是1,設置為10std::cout << "Value changed!" << std::endl; } else {std::cout << "Value not changed!" << std::endl; }
-
fetch_add / fetch_sub
:原子地執行加法或減法操作,并返回舊值。std::atomic<int> a(5); int old_value = a.fetch_add(1); // old_value為5,a為6
7. 相關問題討論
- 使用
std::atomic
實現線程安全計數器:C++ atomic 原子操作_c++ 原子操作-CSDN博客 - 使用
compare_exchange_strong
實現自旋鎖:C++ atomic 原子操作_c++ 原子操作-CSDN博客 - 原子操作支持的運算和不支持的運算:C++ atomic 原子操作_c++ 原子操作-CSDN博客
- 什么是ABA問題,如何解決:ABA問題及其解決思路C+±CSDN博客