目錄<atomic>原子操作的概念CAS實現原理CAS操作的偽代碼:使用CAS完成變數的原子操作:CAS 操作的保證lock和鎖的概念atomic模板類構造函數公共成員函數:atomic與互斥鎖的效率比對 <atomic> C++11提供了一個原子類型std::atomic,通過這個原子類型管理的內 ...
目錄
<atomic>
C++11提供了一個原子類型std::atomic
原子類型只支持bool、char、int、long、指針等整型類型作為模板參數(不支持浮點類型和複合類型)。
原子操作的概念
在原子操作過程中,線程不會發生調度。原子操作是一種不可中斷的操作,一旦開始執行,就會一直運行到結束,不會被其他線程打斷。因此,在原子操作期間,操作系統的線程調度器不會將CPU分配給其他線程,以確保原子操作的完整性和正確性。
CAS實現原理
CAS(Compare-And-Swap 或 Compare-And-Set)是一種原子操作,用於實現多線程編程中的同步機制。它的基本原理是通過比較和交換來確保一個變數在多個線程訪問時的正確性。CAS是許多併發數據結構和演算法的基礎,比如無鎖隊列、無鎖棧等。
CAS操作的偽代碼:
硬體提供的原子性支持,如彙編lock指令等 //整個代碼執行都是不可中斷的(不是串列)
(asm::lock:)
bool compare_and_swap(int* addr, int expected, int new_value) {
int current = *addr; // 讀取當前值
if (current == expected) { //比較當前值和預期值
*addr = new_value; // 如果當前值等於預期值,則更新
return true;
}
return false; // 否則,不更新
}
(asm::unlock:)
使用CAS完成變數的原子操作:
// 共用的整數變數
int shared_var = 10; //ABA問題:無法得知shared_var的全程狀態.被修改去又修改回,起止狀態不變,過程改變,狀態在過程中發生改變CAS卻誤以為沒有改變過.可能導致發生一些隱晦的錯誤.
//避免ABA,有做變數版本號機制(tag,flag等),或更複雜的實現. -- 有的說回退機制可以:不可以,單單回退機制無法解決ABA問題
void thread_function() {
int expected_value = 10; // 希望 shared_var 的當前值是一般是shared_var的原始值,即10
int new_value = 20; // 目的 將shared_var 更新為 20
// 使用 CAS 操作來原子地更新 shared_var
bool success = compare_and_swap(&shared_var, expected_value, new_value);
if (success) {
// CAS 操作成功
// 這裡可以繼續處理其他邏輯
} else {
// CAS 操作失敗,shared_var 的值不是我們預期的 expected_value
// 可能需要重試或處理其他情況
}
}
CAS 操作的保證
儘管可能會發生 CPU 上下文切換,但 CAS 操作的保證在於其執行過程中的不可中斷性。即使發生了 CPU 上下文切換,操作系統和處理器會保證 CAS 操作的執行過程是原子的,其他線程或處理器無法在 CAS 操作期間對其操作的記憶體位置進行修改。
(CAS == 邏輯檢查變數狀態+硬體支持語句原子性)
lock和鎖的概念
彙編中的
lock
指令首碼和編程中的鎖(如互斥鎖)雖然在概念上都涉及到同步和確保操作的原子性,但它們是不同的東西,作用機制和應用場景也不同。
lock
指令首碼用於多處理器系統中的彙編指令,確保特定的記憶體操作在多個處理器上是原子的。它的作用是鎖住匯流排或使用緩存一致性協議,確保在指令執行期間其他處理器無法訪問涉及的記憶體位置。lock
首碼常用於需要原子操作的低級同步機制中,例如在實現原子性增減、比較交換等操作時。編程中的鎖(如互斥鎖、讀寫鎖)是一種高級同步原語,用於確保同一時刻只有一個線程可以訪問臨界區(共用資源)。用於保護臨界區,防止數據競爭,確保線程安全。常見的應用場景包括多線程程式中的共用數據訪問、資料庫中的事務管理等。
atomic模板類
構造函數
atomic() noexcept = default;
constexpr atomic( T desired ) noexcept;
atomic( const atomic& ) = delete; //禁止拷貝
desired:用以初始化的值
空對象初始化方式:
MSVC中類成員atomic類型允許帶有預設值(MSVC優化).但這不是標準C++行為.atomic類型的成員只能在構造函數中完成初始化.
而GCC中不允許使用預設值,是由編譯器實現的.
如果我們要自己實現一個不允許使用預設值的類型,則可以顯式定義構造函數+explicit
公共成員函數:
- operator=
//模擬原生變數的行為,賦值給原生變數
T operator=( T desired ) noexcept;
T operator=( T desired ) volatile noexcept;
//禁止拷貝賦值重載
atomic& operator=( const atomic& ) = delete;
atomic& operator=( const atomic& ) volatile = delete;
- store
和operator=功能一樣,將數據存儲到原生變數中,沒有返回值
void store( T desired, std::memory_order order = std::memory_order_seq_cst ) noexcept;
void store( T desired, std::memory_order order = std::memory_order_seq_cst ) volatile noexcept;
desired:存儲到原子變數中的值
order :程式代碼記憶體執行順序約束(跨平臺使用)
volatile:保證記憶體可見性,修飾函數時表示可以通過該函數訪問到volatile修飾的變數.
- load
取出原生變數的值.
T load( std::memory_order order = std::memory_order_seq_cst ) const noexcept;
T load( std::memory_order order = std::memory_order_seq_cst ) const volatile noexcept;
- operator T()
operator T() const volatile noexcept;
operator T() const noexcept;
類類型轉換運算符重載.將原子變數類型轉化成T類型.
意思是,通過原子變數得到的值就是T類型的值,即得到原生變數的值.等同於load().
示例:
- exchange
T exchange (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
T exchange (T val, memory_order sync = memory_order_seq_cst) noexcept;
和store()功能類似,將數據存儲/覆蓋到原生變數中.
不同的功能是,exchange還會返回被覆蓋的舊數據.
- compare_exchange_weak & compare_exchange_strong
使用C++11原子量實現自旋鎖 - 兔曉俠 - 博客園 (cnblogs.com)
compare_exchange_weak 與 compare_exchange_strong 主要的區別在於記憶體中的值與expected相等的時候,CAS操作是否一定能成功.
compare_exchange_weak有概率會返回失敗,而compare_exchange_strong則一定會成功。
因此,compare_exchange_weak必須與迴圈搭配使用來保證在失敗的時候重試CAS操作。得到的好處是在某些平臺上compare_exchange_weak性能更好。按照上面的模型,我們本來就要和while搭配使用,可以使用compare_exchange_weak。
最後記憶體序的選擇沒有特殊需求直接使用預設的std::memory_order_seq_cst。
- atomic提供的一組用於對原子變數進行修改的特化函數
atomic - C++ Reference (cplusplus.com)
分別是 加、減、按位與、按位或、按位異或、自增、自減、賦值類 操作。
各個 operator 對應的 fetch_ 操作表
操作符 | 操作符重載函數 | 等級的成員函數 | 整形 | 指針 | 其他 |
---|---|---|---|---|---|
+ | atomic::operator+= | atomic::fetch_add | 是 | 是 | 否 |
- | atomic::operator-= | atomic::fetch_sub | 是 | 是 | 否 |
& | atomic::operator&= | atomic::fetch_and | 是 | 否 | 否 |
| | atomic::operator|= | atomic::fetch_or | 是 | 否 | 否 |
^ | atomic::operator^= | atomic::fetch_xor | 是 | 否 | 否 |
- C++11還為常用的atomic提供了別名
std::atomic - cppreference.com
有很多,舉例出一部分
別名 | 原始類型定義 |
---|---|
atomic_bool(C++11) | std::atomic |
atomic_char(C++11) | std::atomic |
atomic_schar(C++11) | std::atomic |
atomic_uchar(C++11) | std::atomic |
atomic_short(C++11) | std::atomic |
atomic_ushort(C++11) | std::atomic |
atomic_int(C++11) | std::atomic |
atomic_uint(C++11) | std::atomic |
atomic_long(C++11) | std::atomic |
atomic_ulong(C++11) | std::atomic |
atomic_llong(C++11) | std::atomic |
atomic_ullong(C++11) | std::atomic |
atomic與互斥鎖的效率比對
常式:
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <functional>
using namespace std;
struct Counter
{
Counter():m_value(0){}
void increment()
{
for (int i = 0; i < 100000000; ++i)
{
lock_guard<mutex> locker(m_mutex);
m_value++;
}
}
void decrement()
{
for (int i = 0; i < 100000000; ++i)
{
lock_guard<mutex> locker(m_mutex);
m_value--;
}
}
int m_value = 0;
//std::atomic<int> m_value ;
mutex m_mutex;
};
int main()
{
Counter c;
auto increment = bind(&Counter::increment, &c);
auto decrement = bind(&Counter::decrement, &c);
thread t1(increment);
thread t2(decrement);
t1.join();
t2.join();
std::cout<<"Counter: "<<c.m_value<<"\n";
return 0;
}
GCC下互斥鎖版本耗時:
GCC下原子變數版本耗時:
顯然,在基本類型線程同步的場景下,原子變數性能更為高效.