C語言多執行緒程式設計初探 MinGW pthread

2021-09-10 09:00:22 字數 3768 閱讀 8783

**:

將dll\x86下的pthreadgc2.dll和pthreadgce2.dll拷貝到mingw的bin資料夾下;

將include資料夾下的pthread.h、sched.h和semaphore.h拷貝到mingw的include資料夾下;

還有將lib\x86下的libpthreadgc2.a和libpthreadgce2.a拷貝到mingw的lib下面。

這樣我們就可以在mingw中呼叫pthread庫了。下面的**是部落格中的例子,用來表明有鎖和無鎖的區別的。

#include #include #include #include "myspinlock.h"

// 是否使用spinlock包含全域性變數,預設是不使用,改為1則為使用

#define use_spinlock 0

sem_t beginsemaarr[500];

sem_t endsema;

int global_count;

int add_loops = 10000;

spinlock slock = 0;

void *threadaddfunc(void *param)

sem_post(&endsema); // notify transaction complete

}return null; // never returns

};int main()

return 0; // never returns

}

安裝完使用gcc -o pthread.exe pthread.c -lpthreadgc2命令來編譯(注意順序,-lpthreadgc2要放在後面,放在前面會報錯=。=!,這裡之所以是-l(-l引數表明使用什麼庫)後面加的是pthreadgc2是因為我們之前拷貝的庫的名字是libpthreadgc2.a,在使用的時候需要將字首lib和字尾.a去掉使用):

collect2: ld returned 1 exit status

然後./pthread.exe就可以看到執行的過程了。這個例子中main函式建立了500個執行緒,在每個執行緒中對乙個全域性變數global_count進行加1的操作,通過訊號量保證每一次迭代到末尾時500個執行緒全部執行完畢,理論上來說此時global_count應該是等於5000000的,可是當我們執行這個程式的時候,發現並不是每次global_count都是等於5000000的。這就意味著並不是所有的加1操作都是被執行到了。原因在於:++操作並不是乙個原子操作,原子操作是指在執行完畢之前不會被任何其它任務或事件中斷的操作。而我們使用的++語句,其對應的彙編語句有3句:

global_count++;

a1163c mov eax,dword ptr [global_count]

a1163f add eax,1

a11642 mov dword ptr [global_count],eax

反彙編之後我們看到,global_count++需要先把global_count的值載入到eax中,然後將eax加1之後,再將eax寫回到global_count。這就會導致,有可能當執行緒a執行到第二條彙編指令時,執行緒b開始執行第一條,於是a剛剛將eax暫存器加1,又被執行緒b覆蓋了,此時雖然執行了兩次++,但是global_count的值只被加了1。所以我們希望在執行++的時候不要被打斷,於是就產生了鎖的概念,每次執行++之前先加上一把鎖,執行完之後再解鎖,執行**期間,如果其他執行緒需要判斷這段**有沒有加鎖,如果加鎖了就需要等待之前加鎖的執行緒把鎖解開之後才能進行操作。何登成在樣例**中實現了乙個自己寫的spinlock(自旋鎖),自旋鎖這個名字翻譯的很直接,當執行緒在等待的時候,並沒有進入阻塞狀態,而是一直在「自旋」(通過乙個while一直在查詢鎖的狀態)。加上鎖之後(也就是將use_spinlock定義為1),再次執行**,沒有一條語句列印出來。

這裡的spinlock實現的就是我們熟悉的互斥的功能,互斥是表明執行緒對於資源的獨占性,同一時間段中只有乙個執行緒能夠訪問該資源。看下spinlock的實現:

...

#define barrier() asm volatile("": : :"memory")

...static inline unsigned xchg_32(void *ptr, unsigned x)

#define ebusy 1

typedef unsigned spinlock;

static void spin_lock(spinlock *lock)

...

使用內聯彙編進行編寫,選擇其中的spin_lock函式稍微解釋下。在spin_lock中是乙個while迴圈,這個迴圈也是就是自旋的意思所在。xchg_32是乙個內聯彙編函式,內聯彙編的格式大體如下:

__asm__ __volatile__("《彙編語句模板》 %0,%1...%9"

:"=《限制字元》" 《運算元》

:"《限制字元》" 《運算元》, "《限制字元》" 《運算元》...

:"函式中第一行是彙編語句模板[2],xchgl是彙編的交換指令,該指令需要兩個運算元,分別使用%0和%1來表示,與函式的引數ptr和x對應;

第二行的「=r」表明是這部分是輸出,=表示輸出,r表示將輸出變數放入通用暫存器;

第三行是輸入部分,用於描述輸入運算元,不同的運算元描述符之間使用逗號格開。運算元的描述部分前面都有一段雙引號來進行修飾,這個雙引號和其裡面的部分稱為限制字元,就像"m"、"0"等等,"m"表示該擦作數為記憶體變數,"0"表示它限制的運算元與第0個運算元匹配,也就意味著x與第0個運算元ptr一樣,也是記憶體變數; 

最後一行是「clobber列表」,意為被破壞的列表,如果指令連續使用一些暫存器。我們必須把這麼暫存器列在clobber列表之中,也則是內聯匯程式設計序裡第三個冒號後的範圍。這樣是為了告訴gcc我們自己將會使用並且修改它們。這樣gcc將不會認為這些暫存器裡的值是可用的。我們沒有必要列出用於輸入和輸出的暫存器。因為gcc知道asm程式使用到它們(因為它們在約束中明顯地指出來)。如果指令使用任何其它的暫存器,無論是顯式還是隱式的指出(同是這樣的暫存器也沒有在輸入或者輸出約束列表**現),那麼這些暫存器必須在clobber列表**現。如果我們的指令會改變暫存器的值,我們必須加上"cc"到clobber列表上。如果我們的指令在不可預知的情況修改了記憶體,則要增加這個到clobber 列表增加」memory」。這樣的話gcc在執行彙編指令時就不會在暫存器中儲存這個記憶體的值。如果對記憶體的影響並沒有列在input或者output中,那麼我們還要增加volatile這個關鍵字[3]。 

該函式的功能就是當執行緒a使用spin_lock之後,ebusy(1)就會與slock(一開始為0)發生交換,此時slock便等於1,程式返回0,繼續執行,當其他執行緒b執行到spin_lock時,無論如何交換,slock和ebusy都是1,所以函式返回1,此時該執行緒就會一直在while裡面自旋,直到執行緒a使用spin_unlock將slock置為0。 

spin_unlock中再將slock置為0之前呼叫了barrier(),這句話什麼都沒做,只是clobber列表中寫了"memory",說明下面要修改記憶體了,於是編譯器不會在暫存器中儲存slock的值而是直接寫入到記憶體中。spin_lock中的slock(引數ptr)使用了"m"限制以及"volatile"的修飾,每次都會從記憶體中讀取,所以一旦spin_unlock將slock置為0,spin_lock中的while就會退出while迴圈。 

更多的細節在何登成的部落格中有說明,還有更深入的討論,比如說lock acquire和unlock release語義的說明。這裡只是自己實驗了下,理解了下spinlock的實現。

初探C 多執行緒程式設計

初探c 多執行緒程式設計 以前在使用vb來實現多執行緒的時候,發現有一定的難度。雖然也有這樣那樣的方法,但都不盡人意,但在c 中,要編寫多執行緒應用程式卻相當的簡單。這篇文章將作簡要的介紹,以起到拋磚引玉的作用!net將關於多執行緒的功能定義在system.threading名字空間中。因此,要使用...

c 多執行緒程式設計初探

多程序就是跑了乙個main函式,直到結束 多執行緒就是在跑這個程序的過程中,把一條河流截成很多條小溪,可以相互通訊,共享變數等。c 11 新標準中引入了五個標頭檔案來支援多執行緒程式設計,它們分別是,和。該頭文主要宣告了兩個類,std atomic 和 std atomic flag,另外還宣告了一...

c 多執行緒程式設計 初探

c 多執行緒併發可以幫助我們挖掘cpu的效能,在我們的思想中,似乎程式都是順序執行的。這樣的結論是建立在 程式是單執行緒程式。比如我們平時寫的hello world程式 但是如果程式是多執行緒的。那麼這個結論就不成立了。先上 1 include 2 include 3 include 4 5void...