国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

通用線程:POSIX 線程詳解(2)

2019-11-17 05:31:49
字體:
來源:轉載
供稿:網友

  第 2 部分
稱作互斥對象的小玩意

作者:Daniel Robbins

內容:


互斥我吧!
解讀一下
為什么要用互斥對象?
線程內幕 1
線程內幕 2
許多互斥對象
使用調用:初始化
使用調用:鎖定
等待條件發生
參考資料
關于作者



POSIX 線程是提高代碼響應和性能的有力手段。在此三部分系列文章的第二篇中,Daniel Robbins 將說明,如何使用被稱為互斥對象的靈巧小玩意,來保護線程代碼中共享數據結構的完整性。

互斥我吧!
在前一篇文章中,談到了會導致異常結果的線程代碼。兩個線程分別對同一個全局變量進行了二十次加一。變量的值最后應該是 40,但最終值卻是 21。這是怎么回事呢?因為一個線程不停地“取消”了另一個線程執行的加一操作,所以產生這個問題。現在讓我們來查看改正后的代碼,它使用互斥對象(mutex)來解決該問題:

thread3.c
#include
#include
#include
#include

int myglobal;
pthread_mutex_t mymutex=PTHREAD_MUTEX_IN99vIALIZER;

void *thread_function(void *arg) {
int i,j;
for ( i=0; i<20; i++) {
pthread_mutex_lock(&mymutex);
j=myglobal;
j=j+1;
fflush(stdout);
sleep(1);
myglobal=j;
pthread_mutex_unlock(&mymutex);
}
return NULL;
}

int main(void) {

pthread_t mythread;
int i;

if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
printf("error creating thread.");
abort();
}

for ( i=0; i<20; i++) {
pthread_mutex_lock(&mymutex);
myglobal=myglobal+1;
pthread_mutex_unlock(&mymutex);
printf("o");
fflush(stdout);
sleep(1);
}

if ( pthread_join ( mythread, NULL ) ) {
printf("error joining thread.");
abort();
}

printf("/nmyglobal equals %d/n",myglobal);

exit(0);

}



解讀一下
假如將這段代碼與前一篇文章中給出的版本作一個比較,就會注重到增加了 pthread_mutex_lock() 和 pthread_mutex_unlock() 函數調用。在線程程序中這些調用執行了不可或缺的功能。他們提供了一種相互排斥的方法(互斥對象即由此得名)。兩個線程不能同時對同一個互斥對象加鎖。

互斥對象是這樣工作的。假如線程 a 試圖鎖定一個互斥對象,而此時線程 b 已鎖定了同一個互斥對象時,線程 a 就將進入睡眠狀態。一旦線程 b 釋放了互斥對象(通過 pthread_mutex_unlock() 調用),線程 a 就能夠鎖定這個互斥對象(換句話說,線程 a 就將從 pthread_mutex_lock() 函數調用中返回,同時互斥對象被鎖定)。同樣地,當線程 a 正鎖定互斥對象時,假如線程 c 試圖鎖定互斥對象的話,線程 c 也將臨時進入睡眠狀態。對已鎖定的互斥對象上調用 pthread_mutex_lock() 的所有線程都將進入睡眠狀態,這些睡眠的線程將“排隊”訪問這個互斥對象。

通常使用 pthread_mutex_lock() 和 pthread_mutex_unlock() 來保護數據結構。這就是說,通過線程的鎖定和解鎖,對于某一數據結構,確保某一時刻只能有一個線程能夠訪問它。可以推測到,當線程試圖鎖定一個未加鎖的互斥對象時,POSIX 線程庫將同意鎖定,而不會使線程進入睡眠狀態。


請看這幅輕松的漫畫,四個小精靈重現了最近一次 pthread_mutex_lock() 調用的一個場面。



圖中,鎖定了互斥對象的線程能夠存取復雜的數據結構,而不必擔心同時會有其它線程干擾。那個數據結構實際上是“凍結”了,直到互斥對象被解鎖為止。pthread_mutex_lock() 和 pthread_mutex_unlock() 函數調用,如同“在施工中”標志一樣,將正在修改和讀取的某一特定共享數據包圍起來。這兩個函數調用的作用就是警告其它線程,要它們繼續睡眠并等待輪到它們對互斥對象加鎖。當然,除非在每個對特定數據結構進行讀寫操作的語句前后,都分別放上 pthread_mutex_lock() 和 pthread_mutext_unlock() 調用,才會出現這種情況。

為什么要用互斥對象?
聽上去很有趣,但究竟為什么要讓線程睡眠呢?要知道,線程的主要優點不就是其具有獨立工作、更多的時候是同時工作的能力嗎?是的,確實是這樣。然而,每個重要的線程程序都需要使用某些互斥對象。讓我們再看一下示例程序以便理解原因所在。

請看 thread_function(),循環中一開始就鎖定了互斥對象,最后才將它解鎖。在這個示例程序中,mymutex 用來保護 myglobal 的值。仔細查看 thread_function(),加一代碼把 myglobal 復制到一個局部變量,對局部變量加一,睡眠一秒鐘,在這之后才把局部變量的值傳回給 myglobal。不使用互斥對象時,即使主線程在 thread_function() 線程睡眠一秒鐘期間內對 myglobal 加一,thread_function() 清醒后也會覆蓋主線程所加的值。使用互斥對象能夠保證這種情形不會發生。(您也許會想到,我增加了一秒鐘延遲以觸發不正確的結果。把局部變量的值賦給 myglobal 之前,實際上沒有什么真正理由要求 thread_function() 睡眠一秒鐘。)使用互斥對象的新程序產生了期望的結果:


$ ./thread3
o..o..o.o..o..o.o.o.o.o..o..o..o.ooooooo
myglobal equals 40




為了進一步探索這個極為重要的概念,讓我們看一看程序中進行加一操作的代碼:


thread_function() 加一代碼:
j=myglobal;
j=j+1;
printf(".");
fflush(stdout);
sleep(1);
myglobal=j;

主線程加一代碼:
myglobal=myglobal+1;




假如代碼是位于單線程程序中,可以預期 thread_function() 代碼將完整執行。接下來才會執行主線程代碼(或者是以相反的順序執行)。在不使用互斥對象的線程程序中,代碼可能(幾乎是,由于調用了 sleep() 的緣故)以如下的順序執行:


thread_function() 線程 主線程

j=myglobal;
j=j+1;
printf(".");
fflush(stdout);
sleep(1); myglobal=myglobal+1;
myglobal=j;




當代碼以此特定順序執行時,將覆蓋主線程對 myglobal 的修改。程序結束后,就將得到不正確的值。假如是在操縱指針的話,就可能產生段錯誤。注重到 thread_function() 線程按順序執行了它的所有指令。看來不象是 thread_function() 有什么次序顛倒。問題是,同一時間內,另一個線程對同一數據結構進行了另一個修改。

線程內幕 1
在解釋如何確定在何處使用互斥對象之前,先來深入了解一下線程的內部工作機制。請看第一個例子:

假設主線程將創建三個新線程:線程 a、線程 b 和線程 c。假定首先創建線程 a,然后是線程 b,最后創建線程 c。


pthread_create( &thread_a, NULL, thread_function, NULL);
pthread_create( &thread_b, NULL, thread_function, NULL);
pthread_create( &thread_c, NULL, thread_function, NULL);




在第一個 pthread_create() 調用完成后,可以假定線程 a 不是已存在就是已結束并停止。第二個 pthread_create() 調用后,主線程和線程 b 都可以假定線程 a 存在(或已停止)。

然而,就在第二個 create() 調用返回后,主線程無法假定是哪一個線程(a 或 b)會首先開始運行。雖然兩個線程都已存在,線程 CPU 時間片的分配取決于內核和線程庫。至于誰將首先運行,并沒有嚴格的規則。盡管線程 a 更有可能在線程 b 之前開始執行,但這并無保證。對于多處理器系統,情況更是如此。假如編寫的代碼假定在線程 b 開始執行之前實際上執行線程 a 的代碼,那么,程序最終正確運行的概率是 99%。或者更糟糕,程序在您的機器上 100% 地正確運行,而在您客戶的四處理器服務器上正確運行的概率卻是零。

從這個例子還可以得知,線程庫保留了每個單獨線程的代碼執行順序。換句話說,實際上那三個 pthread_create() 調用將按它們出現的順序執行。從主線程上來看,所有代碼都是依次執行的。有時,可以利用這一點來優化部分線程程序。例如,在上例中,線程 c 就可以假定線程 a 和線程 b 不是正在運行就是已經終止。它不必擔心存在還沒有創建線程 a 和線程 b 的可能性。可以使用這一邏輯來優化線程程序。


線程內幕 2
現在來看另一個假想的例子。假設有許多線程,他們都正在執行下列代碼

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 贵定县| 刚察县| 巴楚县| 澳门| 思南县| 井冈山市| 平顶山市| 上虞市| 汾阳市| 临高县| 景德镇市| 元阳县| 柳河县| 莲花县| 栾川县| 清涧县| 庆阳市| 芮城县| 屏东市| 开阳县| 玉林市| 靖宇县| 周口市| 博野县| 洪湖市| 班玛县| 三原县| 内黄县| 义马市| 禄丰县| 永平县| 英超| 阜城县| 丹阳市| 泗洪县| 仁怀市| 彰化市| 甘洛县| 宁夏| 甘孜县| 阿勒泰市|