詳解C++11原子類型與原子操作
1.認(rèn)識(shí)原子操作
原子操作就是在多線程程序中“最小的且不可并行化的”操作,意味著多個(gè)線程訪問(wèn)同一個(gè)資源時(shí),有且僅有一個(gè)線程能對(duì)資源進(jìn)行操作。通常情況下原子操作可以通過(guò)互斥的訪問(wèn)方式來(lái)保證,例如Linux下的互斥鎖(mutex),Windows下的臨界區(qū)(Critical Section)等。下面看一個(gè)Linux環(huán)境使用POSIX標(biāo)準(zhǔn)的pthread庫(kù)實(shí)現(xiàn)多線程下的原子操作:
#include <pthread.h> #include <iostream> using namespace std; int64_t total=0; pthread_mutex_t m=PTHREAD_MUTEX_INITIALIZER; //線程函數(shù),用于累加 void* threadFunc(void* args) { int64_t endNum=*(int64_t*)args; for(int64_t i=1;i<=endNum;++i) { pthread_mutex_lock(&m); total+=i; pthread_mutex_unlock(&m); } } int main() { int64_t endNum=100; pthread_t thread1ID=0,thread2ID=0; //創(chuàng)建線程1 pthread_create(&thread1ID,NULL,threadFunc,&endNum); //創(chuàng)建線程2 pthread_create(&thread2ID,NULL,threadFunc,&endNum); //阻塞等待線程1結(jié)束并回收資源 pthread_join(thread1ID,NULL); //阻塞等待線程2結(jié)束并回收資源 pthread_join(thread2ID,NULL); cout<<"total="<<total<<endl; //10100 }
上面的代碼,兩個(gè)線程同時(shí)對(duì)total進(jìn)行操作,為了保證total+=i
的原子性,采用互斥鎖來(lái)保證同一時(shí)刻只有同一線程執(zhí)行total+=i
操作,所以得出正確結(jié)果total=10100
。如果沒有做互斥處理,那么total同一時(shí)刻可能會(huì)被兩個(gè)線程同時(shí)操作,即會(huì)出現(xiàn)兩個(gè)線程同時(shí)讀取了寄存器中的total值,分別操作之后又寫入寄存器,這樣就會(huì)有一個(gè)線程的增加操作無(wú)效,會(huì)得出一個(gè)小于10100隨機(jī)的錯(cuò)誤值。
2.C++11實(shí)現(xiàn)原子操作
在C++11之前,使用第三方API可以實(shí)現(xiàn)并行編程,比如pthread多線程庫(kù),但是在使用時(shí)需要?jiǎng)?chuàng)建互斥鎖,以及進(jìn)行加鎖、解鎖等操作來(lái)保證多線程對(duì)臨界資源的原子操作,這無(wú)疑增加了開發(fā)的工作量。不過(guò)從C++11開始,C++從語(yǔ)言層面開始支持并行編程,內(nèi)容包括了管理線程、保護(hù)共享數(shù)據(jù)、線程間的同步操作、低級(jí)原子操作等各種類。新標(biāo)準(zhǔn)極大地提高了程序的可移植性,以前的多線程依賴于具體的平臺(tái),而現(xiàn)在有了統(tǒng)一的接口。
C++11通過(guò)引入原子類型幫助開發(fā)者輕松實(shí)現(xiàn)原子操作。
#include <atomic> #include <thread> #include <iostream> using namespace std; atomic_int64_t total = 0; //atomic_int64_t相當(dāng)于int64_t,但是本身就擁有原子性 //線程函數(shù),用于累加 void threadFunc(int64_t endNum) { for (int64_t i = 1; i <= endNum; ++i) { total += i; } } int main() { int64_t endNum = 100; thread t1(threadFunc, endNum); thread t2(threadFunc, endNum); t1.join(); t2.join(); cout << "total=" << total << endl; //10100 }
程序正常編譯并運(yùn)行輸出正確結(jié)果total=10100
。使用C++11提供的原子類型與多線程標(biāo)準(zhǔn)接口,簡(jiǎn)潔地實(shí)現(xiàn)了多線程對(duì)臨界資源的原子操作。原子類型C++11中通過(guò)atomic<T>
類模板來(lái)定義,比如atomic_int64_t是通過(guò)typedef atomic<int64_t> atomic_int64_t
實(shí)現(xiàn)的,使用時(shí)需包含頭文件<atomic>
。除了提供atomic_int64_t,還提供了其它的原子類型。常見的原子類型有
原子類型名稱 |
對(duì)應(yīng)內(nèi)置類型 |
---|---|
atomic_bool |
bool |
atomic_char |
atomic_char |
atomic_char |
signed char |
atomic_uchar |
unsigned char |
atomic_short |
short |
atomic_ushort |
unsigned short |
atomic_int |
int |
atomic_uint |
unsigned int |
atomic_long |
long |
atomic_ulong |
unsigned long |
atomic_llong |
long long |
atomic_ullong |
unsigned long long |
atomic_ullong |
unsigned long long |
atomic_char16_t |
char16_t |
atomic_char32_t |
char32_t |
atomic_wchar_t |
wchar_t |
原子操作是平臺(tái)相關(guān)的,原子類型能夠?qū)崿F(xiàn)原子操作是因?yàn)镃++11對(duì)原子類型的操作進(jìn)行了抽象,定義了統(tǒng)一的接口,并要求編譯器產(chǎn)生平臺(tái)相關(guān)的原子操作的具體實(shí)現(xiàn)。C++11標(biāo)準(zhǔn)將原子操作定義為atomic模板類的成員函數(shù),包括讀(load)、寫(store)、交換(exchange)等。對(duì)于內(nèi)置類型而言,主要是通過(guò)重載一些全局操作符來(lái)完成的。比如對(duì)上文total+=i的原子加操作,是通過(guò)對(duì)operator+=重載來(lái)實(shí)現(xiàn)的。使用g++編譯的話,在x86_64的機(jī)器上,operator+=()函數(shù)會(huì)產(chǎn)生一條特殊的以lock為前綴的x86_64指令,用于控制總線及實(shí)現(xiàn)x86_64平臺(tái)上的原子性加法。
有一個(gè)比較特殊的原子類型是atomic_flag,因?yàn)閍tomic_flag與其他原子類型不同,它是無(wú)鎖(lock_free)的,即線程對(duì)其訪問(wèn)不需要加鎖,而其他的原子類型不一定是無(wú)鎖的。因?yàn)閍tomic<T>并不能保證類型T是無(wú)鎖的,另外不同平臺(tái)的處理器處理方式不同,也不能保證必定無(wú)鎖,所以其他的類型都會(huì)有is_lock_free()成員函數(shù)來(lái)判斷是否是無(wú)鎖的。atomic_flag只支持test_and_set()以及clear()兩個(gè)成員函數(shù),test_and_set()函數(shù)檢查 std::atomic_flag 標(biāo)志,如果 std::atomic_flag 之前沒有被設(shè)置過(guò),則設(shè)置 std::atomic_flag 的標(biāo)志;如果之前 std::atomic_flag 已被設(shè)置,則返回 true,否則返回 false。clear()函數(shù)清除 std::atomic_flag 標(biāo)志使得下一次調(diào)用 std::atomic_flag::test_and_set()返回 false??梢杂胊tomic_flag的成員函數(shù)test_and_set()和clear()來(lái)實(shí)現(xiàn)一個(gè)自旋鎖(spin lock):
#include <unistd.h> #include <atomic> #include <thread> #include <iostream> std::atomic_flag lock = ATOMIC_FLAG_INIT; void func1() { while (lock.test_and_set(std::memory_order_acquire)) // 在主線程中設(shè)置為true,需要等待t2線程clear { std::cout << "func1 wait" << std::endl; } std::cout << "func1 do something" << std::endl; } void func2() { std::cout << "func2 start" << std::endl; lock.clear(); } int main() { lock.test_and_set(); // 設(shè)置狀態(tài) std::thread t1(func1); usleep(1); //睡眠1us std::thread t2(func2); t1.join(); t2.join(); return 0; }
以上代碼中,定義了一個(gè)atomic_flag對(duì)象lock,使用初始值A(chǔ)TOMIC_FLAG_INIT進(jìn)行初始化,即處于false的狀態(tài)。線程t1調(diào)用test_and_set()一直返回true(因?yàn)樵谥骶€程中被設(shè)置過(guò)),所以一直在等待,而等待一段時(shí)間后當(dāng)線程t2運(yùn)行并調(diào)用了clear(),test_and_set()返回了false退出循環(huán)等待并進(jìn)行相應(yīng)操作。這樣一來(lái),就實(shí)現(xiàn)了一個(gè)線程等待另一個(gè)線程的效果。當(dāng)然,可以封裝成鎖操作的方式,比如:
void Lock(atomic_flag& lock){ while ( lock.test_and_set()); } void UnLock(atomic_flag& lock){ lock.clear(); }
這樣一來(lái),就可以通過(guò)Lock()和UnLock()的方式來(lái)互斥地訪問(wèn)臨界區(qū)。
以上就是詳解C++11原子類型與原子操作的詳細(xì)內(nèi)容,更多關(guān)于C++11原子類型與原子操作的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++利用類實(shí)現(xiàn)矩陣的數(shù)乘,乘法以及點(diǎn)乘
這篇文章主要為大家詳細(xì)介紹了C++如何利用類實(shí)現(xiàn)矩陣的數(shù)乘,乘法以及點(diǎn)乘,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C++有一定幫助,需要的可以參考一下2022-11-11C語(yǔ)言中回調(diào)函數(shù)和qsort函數(shù)的用法詳解
這篇文章主要為大家詳細(xì)介紹一下C語(yǔ)言中回調(diào)函數(shù)和qsort函數(shù)的用法教程,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C語(yǔ)言有一定幫助,需要的可以參考一下2022-07-07如何使用VC庫(kù)函數(shù)中的快速排序函數(shù)
下面呢,小編就為大家介紹一下VC中庫(kù)函數(shù)qsort()的用法。需要的朋友可以過(guò)來(lái)參考下2013-09-09C++?OpenCV紅綠燈檢測(cè)Demo實(shí)現(xiàn)詳解
OpenCV(Open Source Computer Vision Library)是開源的計(jì)算機(jī)視覺和機(jī)器學(xué)習(xí)庫(kù),提供了C++、 C、 Python、 Java接口,并支持Windows、 Linux、 Android、 Mac OS平臺(tái),下面這篇文章主要給大家介紹了關(guān)于C++?OpenCV紅綠燈檢測(cè)Demo實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2022-11-11C++ Boost命令行解析庫(kù)的應(yīng)用詳解
命令行解析庫(kù)是一種用于簡(jiǎn)化處理命令行參數(shù)的工具,它可以幫助開發(fā)者更方便地解析命令行參數(shù)并提供適當(dāng)?shù)膸椭畔?本文主要介紹了不同的命令行解析庫(kù)和它們?cè)贑++項(xiàng)目中的應(yīng)用,希望對(duì)大家有所幫助2023-11-11