多线程基础之四:Linux提供的原子锁类型atomic_t
在x86體系下,任何處理器平臺下都會有一些原子性操作,在單處理器情況下,單步指令的原子性容易實現。但是在SMP多處理器情況下,只有那些單字的讀(將變量讀進寄存器)或寫(從寄存器寫入到變量地址)才是原子性的。故而在SMP下,要保證特定指令集合的原子性即不被中斷,x86提供lock前綴用來在執行被lock修飾的指令期間鎖住總線,從而實現了“禁止中斷”的效果。事實上,Linux操作系統便根據這種針對特殊數據對象在操作期間需要提供原子特性的情況專門提供atomic_t為代表的“原子鎖”。
0. Linux下atomic_t原子鎖和操作函數API
原子操作需要硬件支持,因此是和計算機的具體架構相關的,Linux專門定義了一種原子操作粒度的類型atomic_t (類似的還有atomic6_t),并為該原子類型參數的操作提供相對應的API。故而在Linux下編程使用原子鎖,只需要調用該類型聲明和相應的操作函數API即可。
原子類型參數定義的典型使用場景便是多進程中共享資源的計數加減,如信號量semaphores中的資源總數便是經典的使用場景。所以atomic_t原子鎖支持的便是聲明了一個具有原子操作特性的整數。
下面開始來介紹一下相關的內容。
typedef struct{volatile int counter; //volatile修飾符高速gcc不要對該類型數據進行優化處理,即對它的訪問都是對 //內存的訪問,而不是對寄存器的訪問。即要讀,必須重新找個寄存器載入該參數,而不是直接利用該參數//此刻在其他高速寄存器中的備份。 } atomic_t;Linux為原子類型參數提供了一些系列的操作函數API,收集列舉如下:
這些操作函數的實現均涉及到在C中使用內嵌匯編語言,以其中atomic_add(int i, atomic_t)的具體實現為例
static inline void atomic_add(int i, atomic_t *v) {asm volatile(LOCK_PREFIX "addl %1,%0": "+m" (v->counter): "ir" (i)); }事實上,在適用的場景下,采用“原子鎖”機制比使用mutex互斥量設計臨界區更高效。具體的各函數實現可以參考這篇文章(http://www.linuxidc.com/Linux/2011-10/44627.htm)。
1. Linux下atomic.h的替代者__syn_*系列函數
在Linux2.6.18之后,系統便刪除了 <asm/atomic.h>和 <asm/bitops.h>, <alsa/iatomic.h>,在Linux操作系統下GCC提供了內置的原子操作函數__sync_*,更方便程序員調用。
現在atomic.h在Linux的內核頭文件中,即便能搜索到,但依舊不在gcc默認搜索路徑下(/usr/include,/usr/local/include,/usr/lib/gcc-lib/i386-linux/x.xx.x/include),即使像下面這樣強行指定路徑,還是會出現編譯錯誤。
#include</usr/src/linux-headers-4.4.0-98/include/asm-generic/atomic.h> 或在編譯時提供編譯路徑-I /usr/src/linux-headers-4.4.0-98/include/asm-generic依舊會出現問題gcc從4.1.2提供了__sync_*系列的built-in函數,用于提供加減和邏輯運算的原子操作。可以對1,2,4或8字節長度的數值類型或指針進行原子操作,其聲明如下
type __sync_fetch_and_add (type *ptr, type value, ...) type __sync_fetch_and_sub (type *ptr, type value, ...) type __sync_fetch_and_or (type *ptr, type value, ...) type __sync_fetch_and_and (type *ptr, type value, ...) type __sync_fetch_and_xor (type *ptr, type value, ...) type __sync_fetch_and_nand (type *ptr, type value, ...)type __sync_add_and_fetch (type *ptr, type value, ...) type __sync_sub_and_fetch (type *ptr, type value, ...) type __sync_or_and_fetch (type *ptr, type value, ...) type __sync_and_and_fetch (type *ptr, type value, ...) type __sync_xor_and_fetch (type *ptr, type value, ...) type __sync_nand_and_fetch (type *ptr, type value, ...)故而現在如果要使得atomic.h的舊版本代碼可以運行在當下較新的Linux版本下,需要在相應的代碼文件前面設置宏替換舊版本的atomic_*系列函數
#define atomic_inc(x) __sync_fetch_and_add((x),1) #define atomic_dec(x) __sync_fetch_and_sub((x),1) #define atomic_add(x,y) __sync_fetch_and_add((x),(y)) #define atomic_sub(x,y) __sync_fetch_and_sub((x),(y))事實上,隨著C++11強推內存一致性模型,從GCC4.7開始__sync_*系列函數也被新推出的更安全健壯的__atomic_*系列函數取代了。
2. atomic_t原子鎖使用案例
normal.cpp #include <unistd.h> #include <pthread.h> #include <iostream>using namespace std;//設計test class帶有兩個正常的參數a,b;讓它們實現正常的自增,看看是否出現因為非原子性操作導致的變量不同步class test{private:int a;int b;public:test():a(0),b(0.0){}void inc(){//對變量自增,正常來說因為a,b并沒有被聲明為原子鎖參數,故而這種自增操作是非線程安全的a++;b++;}void get() const{cout<<"a="<<a<<" b="<<b<<endl;}};static int step=0;void* worker(void* arg){sleep(100-step); //手動延遲,因為創建線程還蠻費時間的,所以要人為創造多線程并發操作目標對象的情況step++;//每個線程睡眠一定時間以期達到這些線程能同時對變量進行操作,當然這里本身就不是線程安全的,因為結果得到:自增不是線程安全的test* local_test =(test*)arg; //將目標對象淺拷貝local_test->inc(); }int main(){pthread_t pthd[100]; //聲明一個線程指針數組test* temp=new test;for(int i=0;i<100;i++){pthread_create(&pthd[i],NULL,worker,temp);//開啟100個線程對變量自增}for(int i=0;i<100;i++){pthread_join(pthd[i],NULL); //等待所有子線程完成操作,否則main線程將提前執行后續操作}temp->get();//獲取結果return 0; }
在Linux下編譯該文件,如果運氣夠好,是可以發現該程序是可能出現線程不安全的情況
$g++ normal.cpp -o normal -lpthread $./normal //可能輸出a=99,b=99,也可能輸出a=98,b=98atomic_t.cpp
#include <unistd.h> #include <pthread.h> #include <iostream> #include <stdlib.h>using namespace std;#define atomic_inc(x) __sync_fetch_and_add(x, 1) //用GCC內嵌的__sync_*系列函數來替代原先的atomic_inc(atomic_t* v)函數class test{private:int a;int b;public:test():a(0),b(0){}void inc(){//對變量自增,正常來說因為a,b并沒有被聲明為原子鎖參數,故而這種自增操作是非線程安全的//__sync_fetch_and_add(&a,1);//__sync_fetch_and_add(&b,1);atomic_inc(&a);atomic_inc(&b);}void get() const{cout<<"a="<<a<<" b="<<b<<endl;}};static int step=0;void* worker(void* arg){sleep(100-step); //手動延遲,因為創建線程還蠻費時間的,所以要人為創造多線程并發操作目標對象的情況step++;//每個線程睡眠一定時間以期達到這些線程能同時對變量進行操作,當然這里本身就不是線程安全的,因為結果得到:自增不是線程安全的test* local_test =(test*)arg; //將目標對象淺拷貝local_test->inc(); }int main(){pthread_t pthd[100]; //聲明一個線程指針數組test* temp=new test;for(int i=0;i<100;i++){pthread_create(&pthd[i],NULL,worker,temp);//開啟100個線程對變量自增}for(int i=0;i<100;i++){pthread_join(pthd[i],NULL); //等待所有子線程完成操作,否則main線程將提前執行后續操作}temp->get();//獲取結果return 0; } $g++ atomic_t.cpp -o atomic_t -lpthread $./atomic_t a=100 b=100//但可以明顯感到耗時比前面未加鎖的版本更多總結
以上是生活随笔為你收集整理的多线程基础之四:Linux提供的原子锁类型atomic_t的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringSecurity 记住密码
- 下一篇: 看电影学英语五招必备