1. 程式人生 > >Linux — 淺析執行緒以及多執行緒的同步與互斥

Linux — 淺析執行緒以及多執行緒的同步與互斥

                   淺析多執行緒以及多執行緒的同步與互斥

執行緒概念 什麼是執行緒? 執行緒是程序內部的一個執行分支,執行緒的建立成本小於程序.Linux下沒有真正意義上的執行緒,只有我們用程序模擬出來的執行緒,雖然是假的但是效率很高. 因為有PCB高度一致,複用性高,系統維護也很容易. 執行緒和程序的區別: 1.我們都知道每一個程序的開闢成本是一個大概4GB的虛擬記憶體,建立眾多的資料表來維護其程式碼段、堆疊段和資料段,這種多任務工作方式的代價非常“昂貴”,相比較程序來講,執行緒的開闢成本就節約許多,運行於一個程序中的多個執行緒,它們彼此之間使用相同的地址空間,共享大部分資料,啟動一個執行緒所花費的空間遠遠小於啟動一個程序所花費的空間,而且執行緒間彼此切換所需要時間也遠遠小於程序間切換所需要的時間.
2.執行緒之間通訊是一個執行緒間方便的通訊機制。對不同程序來說它們具有獨立的資料空間,要進行資料的傳遞只能通過通訊的方式進行。這種方式不僅費時,而且很不方便。執行緒則不然,由於同一程序下的執行緒之間共享資料空間,所以一個執行緒的資料可以直接為其他執行緒所用,不僅方便,而且快捷. 執行緒需要用到的函式 建立一個執行緒: 建立執行緒實際上就是確定呼叫該執行緒函式的入口點,這裡通常使用的函式是pthread_create()。線上程建立後,就開始執行相關的執行緒函式。
函式原型:
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg);
引數型別:tidp:要建立的執行緒的執行緒id指標
  attr:建立執行緒時的執行緒屬性
 start_rtn:返回值是void型別的指標函式
  arg:start_rtn的形參
返回值:若是成功建立執行緒返回0,否則返回錯誤的編號
等待執行緒結束函式: 由於一個程序中的多個執行緒是共享資料段的,因此,通常線上程退出後,退出執行緒所佔用的資源並不會隨著執行緒的終止而得到釋放。正如程序之間可以用wait()系統呼叫來同步終止並釋放資源一樣,執行緒之間也有類似機制,那就是pthread_join()函式。pthread_join()用於將當前程序掛起來等待執行緒的結束。這個函式是一個執行緒阻塞的函式,呼叫它的函式將一直等待到被等待的執行緒結束為止,當函式返回時,被等待執行緒的資源就被收回。
函式原型:int pthread_join(pthread_t thread, void **retval);
引數型別:thread :被等待的執行緒識別符號
retval :一個使用者定義的指標,它可以用來儲存被等待執行緒的返回值
返回值:若是成功建立執行緒返回0,否則返回錯誤的編號
執行緒終止函式: 線上程建立後,就開始執行相關的執行緒函式,在該函式執行完之後,該執行緒也就退出了,這也是執行緒退出的一種方法。另一種退出執行緒的方法是使用函式pthread_exit(),這是執行緒的主動行為。這裡要注意的是,在使用執行緒函式時,不能隨意使用exit()退出函式來進行出錯處理。由於exit()的作用是使呼叫程序終止,而一個程序往往包含多個執行緒,因此,在使用exit()之後,該程序中的所有執行緒都終止了。線上程中就可以使用pthread_exit()來代替程序中的exit()。
函式原型:void pthread_exit(void* retval);
引數型別  :retval:函式的返回指標,只要pthread_join中的第二個引數retval不是NULL,這個值將被傳遞給retval
返回值:無

執行緒取消函式

前面已經提到執行緒呼叫pthread_exit()函式主動終止自身執行緒,但是在很多執行緒應用中,經常會遇到在別的執行緒中要終止另一個執行緒的問題,此時呼叫pthread_cancel()函式來實現這種功能,但在被取消的執行緒的內部需要呼叫pthread_setcancel()函式和pthread_setcanceltype()函式設定自己的取消狀態。例如,被取消的執行緒接收到另一個執行緒的取消請求之後,是接受函式忽略這個請求;如果是接受,則再判斷立刻採取終止操作還是等待某個函式的呼叫等。函式原型:int pthread_cancel(pthread_t thread); 引數型別:thread:要取消執行緒的識別符號ID 返回值:若成功返回0,若失敗則返回失敗編號獲取當前執行緒標識ID函式原型:pthread_t pthread_self(void); 引數型別:無 返回值:當前執行緒的執行緒ID標識分離釋放執行緒函式原型:int pthread_detach(pthread_t thread); 引數型別:thread:要釋放執行緒的識別符號ID 說明:linux執行緒執行和windows不同,pthread有兩種狀態joinable狀態和unjoinable狀態。一個執行緒預設的狀態是joinable,如果執行緒是joinable狀態,當執行緒函式自己返回退出時或pthread_exit時都不會釋放執行緒所佔用堆疊和執行緒描述符(總計8K多)。只有當你呼叫pthread_join之後這些資源才會被釋放。若是unjoinable狀態的執行緒,這些資源線上程函式退出時或pthread_exit時自動會被釋放。unjoinable屬性可以在pthread_create時指定,或線上程建立後線上程中pthread_detach自己, 如:pthread_detach(pthread_self()),將狀態改為unjoinable狀態,確保資源的釋放。如果執行緒狀態為joinable,需要在之後適時呼叫pthread_join。 返回值:若成功賊返回0,若失敗賊返回錯誤編碼.  執行緒間的同步與互斥 同步與互斥的概念: 當執行緒併發執行時,由於資源共享和執行緒協作,使用執行緒之間會存在以下兩種制約關係。 1.間接相互制約。一個系統中的多個執行緒必然要共享某種系統資源,如共享CPU,共享I/O裝置,所謂間接相互制約即源於這種資源共享,印表機就 是最好的例子,執行緒A在使用印表機時,其它執行緒都要等待。 2.直接相互制約。這種制約主要是因為執行緒之間的合作,如有執行緒A將計算結果提供給執行緒B作進一步處理,那麼執行緒B線上程A將資料送達之前都將處於阻塞狀態。 間接相互制約可以稱為互斥,直接相互制約可以稱為同步,對於互斥可以這樣理解,執行緒A和執行緒B互斥訪問某個資源則它們之間就會產個順序問題——要麼執行緒A等待執行緒B操作完畢,要麼執行緒B等待執行緒操作完畢,這其實就是執行緒的同步了。因此同步包括互斥,互斥其實是一種特殊的同步。
由於執行緒共享程序的資源和地址空間,所以在訪問到他們的公共資源的時候,一定會出現執行緒的同步和互斥現象,多執行緒訪問一個數據的次序一定是雜亂無章的,所以這也是我們一個非常頭疼的一個問題,這時候我們就可以靈活運用到互斥鎖和訊號量,對一份資源讓執行緒一個一個的訪問,讓程式的可控性提升.訊號量我們以前提到過,所以今天著重來看這個互斥鎖. 互斥鎖:用於保證在某一段時間只有一個執行緒在執行某些程式碼. 互斥鎖最簡單的使用是這樣的:
pthread_mutex_t mutex;                   定義鎖
pthread_mutex_init(&mutex, NULL);   預設屬性初始化鎖
pthread_mutex_lock(&mutex);           申請鎖
pthread_mutex_unlock(&mutex);       釋放鎖
以上函式也是通俗易懂的,我們熟練運用即可掌握.接下來我們通過一個例子來感受一下,執行緒的同步和互斥.
我們先觀察這個程式碼,我們定義了一個count全域性變數,然後用兩個執行緒同時訪問這份資源,每個執行緒訪問5000次,每次將count 加上1,那麼預期結果當最後輸出時count的值為10000,那麼現在我們來執行該段程式碼. 第一次執行: 第二次執行:
第三次執行:   我敢保證這三次我執行的是同一個程式碼,當你多次執行也會出現下面的問題,但是我們預期的是10000啊,為什麼會出現這樣的情況? 這裡就是執行緒的同步與互斥,當執行緒訪問同一片記憶體時,現在我先假設一個極端的問題,因為兩個執行緒各自都有一份棧幀,他們同時拿到了tmp = 0的時候,然後在自己的棧幀中完成對count的賦值,比如執行緒1剛剛對count賦值為1後,執行緒2再次對count賦值為1,所以看起來是兩次操作,但實際count只被加了一次,但是這裡過程會有5000次,裡面發生這樣的情況概率還是很高的,所以他的值遠小於10000,現在我們嘗試解決它,我們開始使用互斥鎖,讓每個執行緒在某段時間唯一的執行某段程式碼. 加鎖後的程式碼:
第一次執行結果:
第二次執行結果:

第三次執行結果:

我敢保證。。。。我真的運行了三次,我們發現加鎖後的結果是我們所預期的結果,這就是互斥鎖的一個最基本而運用,當然當我們慢慢地實踐會有更多的地方用到這些知識,我們只需要根據經驗靈活運用即可.雖然沒多少內容這個也是我對執行緒這個知識點的一點理解和運用,這裡的知識其實經過幾次實踐,記住函式的使用方法和基本操作指令,那麼掌握這個知識點也是沒有問題的.