文章目錄
- 1. 主 / 新執行緒
- 1.1 主 / 新執行緒退出
- 2.執行緒等待
- 3.執行緒終止
- 4.部分總結
- 5.執行緒分離
- 6.執行緒分離總結
- 7.執行緒互斥
- 7.1 執行緒間相關概念
- 7.2 互斥量mutex
- 7.3 互斥量的介面
- 7.4 代碼示例
- 7.5 互斥量實作原理
- 8. 互斥量總結
- 9. 可重入 & 執行緒安全
- 9.1 概念
- 9.2 常見的執行緒不安全的情況
- 9.3 常見的執行緒安全的情況
- 9.4 常見不可重入的情況
- 9.5 常見可重入的情況
- 9.6 可重入與執行緒安全聯系
- 9.7 可重入與執行緒安全區別
- 在前面的博客【Linux_初始多執行緒—>Link】中已經講了有關多執行緒的概念和基本知識,這章將接著上一章的內容對執行緒操作以及其他概念繼續深究,
1. 主 / 新執行緒

- 從上圖中可得知main()函式中,函式pthread_create()往下的為主執行緒,而函式pthread_create()創造的執行緒為新執行緒,接下來的內容將圍繞主/新執行緒講解,
注意:執行緒之間誰先執行是不確定的,而跟系統調度有關,(執行緒是調度的基本單位,系統調度的執行緒標識是LWP)
1.1 主 / 新執行緒退出
- 主執行緒或者新執行緒各自獨立退出時會有兩種情況:
主執行緒沒有等待新執行緒結束而提前退出,則整個行程退出,
問題:沒有等待新執行緒退出而行程退出,新執行緒也是該行程中的一個執行流,會造成記憶體泄漏,

新執行緒退出而主執行緒不退出,主執行緒繼續執行,

2.執行緒等待
為什么需要執行緒等待?
- 已經退出的執行緒,其空間沒有被釋放,仍然在行程的地址空間內,(
如上面的情況1) - 創建新的執行緒不會復用剛才退出執行緒的地址空間,
int pthread_join(pthread_t thread, void **value_ptr);
引數:
thread:執行緒ID,value_ptr:它指向一個指標,后者指向執行緒的回傳值,
回傳值:成功回傳0;失敗回傳錯誤碼,
呼叫該函式的執行緒將掛起等待,直到id為thread的執行緒終止,

3.執行緒終止
- 如果需要只終止某個執行緒而不終止整個行程,可以有三種方法:
- 從執行緒函式return,這種方法對主執行緒不適用,
從main函式return相當于呼叫exit, - 執行緒可以
呼叫pthread_ exit終止自己, - 一個執行緒可以
呼叫pthread_ cancel終止同一行程中的另一個執行緒,
-
從執行緒函式return,這種方法對主執行緒不適用,
從main函式return相當于呼叫exit,在main()函式中return則是結束的整個函式,如果在呼叫的函式中return則只是結束這個函式,而exit()是不管在那個位置,都直接結束整個函式,執行緒也是如此,

-
pthread_exit()函式
void pthread_exit(void *value_ptr)
引數:
- value_ptr:value_ptr不要指向一個區域變數,
回傳值:無回傳值,跟行程一樣,執行緒結束的時候無法回傳到它的呼叫者(自身),

pthread_ cancel()函式
int pthread_cancel(pthread_t thread);
引數:
- thread:執行緒ID
回傳值:
- 成功回傳0,失敗回傳錯誤碼,

為什么被pthread_cancel()退出以后退出碼是-1呢?
答:用pthreat_cancel()函式退出別的別執行緒以后,該執行緒是被
例外終止掉的,value_ ptr所指向的單元里存放的是常數PTHREAD_CANCELED,該函式是一個宏定義,就是-1,
4.部分總結
- thread執行緒以不同的方法終止,通過pthread_join得到的終止狀態是不同的,總結如下:
- 如果thread執行緒通過return回傳,value_ ptr所指向的單元里存放的是thread執行緒函式的回傳值,
- 如果thread執行緒被別的執行緒呼叫pthread_ cancel例外終掉,value_ ptr所指向的單元里存放的是
常數PTHREAD_CANCELED, - 如果thread執行緒是自己呼叫pthread_exit終止的,value_ptr所指向的單元存放的是傳給pthread_exit的引數,
- 如果對thread執行緒的終止狀態不感興趣,可以傳NULL給value_ ptr參
5.執行緒分離
- 當不需要關心某個執行緒執行結果如何,或者執行的對與錯,就可將這個執行緒進行分離,執行緒分離帶來的最直觀的感受就是主執行緒不需要關心這個執行緒的執行如何,
- 默認情況下,新創建的執行緒是joinable的,執行緒退出后,需要對其進行pthread_join操作,否則無法釋放資源,從而造成系統泄漏,
- 如果不關心執行緒的回傳值,join是一種負擔,這個時候,我們可以告訴系統,當執行緒退出時,自動釋放執行緒資源,
- 可以是
執行緒組內其他執行緒對目標執行緒進行分離,也可以是執行緒自己分離:
在別的執行緒中分離:
int pthread_detach(pthread_t thread);
自己分離自己:
pthread_detach(pthread_self());

6.執行緒分離總結
- 執行緒分離退出碼是0,而不是執行緒回傳的退出碼,是因為將執行緒分離以后,拿不到執行緒的退出碼,也不需要拿到它的退出碼,
- 執行緒分離以后,被分離的執行緒會自動釋放自己的資源,不需要被等待,退出碼也不會寫進PCB,
- 但是執行緒被分離以后,如果執行緒中有例外,還是會影響當前行程,(舉例:分離以后,各過各的,但是我出現問題,還是會影響你),
7.執行緒互斥
7.1 執行緒間相關概念
臨界資源:多執行緒執行流共享的資源就叫做臨界資源,(共享資料)臨界區:每個執行緒內部,訪問臨界區域的代碼,就叫做臨界區,(共享代碼)互斥:任何時刻,互斥保證有且只有一個執行流進入臨界區,訪問臨界資源,通常對臨界資源起保護作用,原子性(后面討論如何實作):不會被任何調度機制打斷的操作,該操作只有兩態,要么完成,要么未完成,

7.2 互斥量mutex
- 大部分情況,執行緒使用的資料都是區域變數,變數的地址空間在執行緒堆疊空間內,這種情況,變數歸屬單個執行緒,其他執行緒無法獲得這種變數,
- 但有時候,很多變數都需要在執行緒間共享,這樣的變數稱為共享變數,可以通過資料的共享,完成執行緒之間的互動,
多個執行緒并發的操作共享變數,會帶來一些問題,

要解決以上問題,需要做到三點:
- 代碼必須要有互斥行為:當代碼進入臨界區執行時,不允許其他執行緒進入該臨界區,
- 如果多個執行緒同時要求執行臨界區的代碼,并且臨界區沒有執行緒在執行,那么只能允許一個執行緒進入該臨界區,
- 如果執行緒不在臨界區中執行,那么該執行緒不能阻止其他執行緒進入臨界區,
注意:要做到這三點,本質上就是需要一把鎖,Linux上提供的這把鎖叫互斥量,
7.3 互斥量的介面
初始化互斥量:
- 初始化互斥量有兩種方法:
- 靜態分配:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
- 動態分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t
*restrict attr);
引數:
- mutex:要初始化的互斥量
- attr:NULL
銷毀互斥量:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
銷毀互斥量需要注意:
- 使用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要銷毀
- 不要銷毀一個已經加鎖的互斥量
- 已經銷毀的互斥量,要確保后面不會有執行緒再嘗試加鎖
互斥量加鎖和解鎖:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
回傳值:
- 成功回傳0,失敗回傳錯誤號,
呼叫pthread_ lock 時,可能會遇到以下情況:
- 互斥量處于未鎖狀態,該函式會將互斥量鎖定,同時回傳成功,
- 發起函式呼叫時,其他執行緒已經鎖定互斥量,或者存在其他執行緒同時申請互斥量,但沒有競爭到互斥量,那么pthread_ lock呼叫會陷入阻塞(執行流被掛起),等待互斥量解鎖,
7.4 代碼示例

7.5 互斥量實作原理
- 經過上面的例子,如果不是原子的,有可能會有資料出現問題,
- 為了實作互斥鎖操作,大多數體系結構都提供了swap或exchange指令,該指令的作用是把暫存器和記憶體單元的資料相交換,由于只有一條指令,保證了原子性,即使是多處理器平臺,訪問記憶體的 總線周期也有先后,一個處理器上的交換指令執行時另一個處理器的交換指令只能等待總線周期, 鎖的原子性詳解->link,
8. 互斥量總結
- 對臨界區進行保護,所有的執行執行緒都必須遵守這個規則,
- 基本程序:lock->訪問臨界區->unlock,
鎖的原子性詳解->link, - 所有的執行緒必須先看到同一把鎖,鎖本身就是臨界資源!鎖本身得先保證自身安全!申請鎖的程序,不能有中間狀態,也就是兩態的,lock->原子性,unlock->原子性,
- lock->訪問臨界區(花時間)->unlock,在特定執行緒/行程擁有鎖的時候,期間有新執行緒過來申請鎖,一定不能申請到,那么新執行緒進行阻塞,將行程,執行緒對應的PCB投入到等待佇列,
- 加鎖時候效率變低,本來是并行/并發執行流,加鎖以后成了串行執行流,
- 一次保證只有一個執行緒進入臨界區,訪問臨界資源,就叫做
互斥,
9. 可重入 & 執行緒安全
9.1 概念
執行緒安全:多個執行緒并發同一段代碼時,不會出現不同的結果,常見對全域變數或者靜態變數進行操作,
并且沒有鎖保護的情況下,會出現該問題,重入:同一個函式被不同的執行流呼叫,當前一個流程還沒有執行完,就有其他的執行流再次進入,我們
稱之為重入,一個函式在重入的情況下,運行結果不會出現任何不同或者任何問題,則該函式被稱為可重
入函式,否則,是不可重入函式
9.2 常見的執行緒不安全的情況
- 不保護共享變數的函式
- 函式狀態隨著被呼叫,狀態發生變化的函式
- 回傳指向靜態變數指標的函式
- 呼叫執行緒不安全函式的函式
9.3 常見的執行緒安全的情況
- 每個執行緒對全域變數或者靜態變數只有讀取的權限,而沒有寫入的權限,一般來說這些執行緒是安全的
- 類或者介面對于執行緒來說都是原子操作
- 多個執行緒之間的切換不會導致該介面的執行結果存在二義性
9.4 常見不可重入的情況
- 呼叫了malloc/free函式,因為malloc函式是用全域鏈表來管理堆的
- 呼叫了標準I/O庫函式,標準I/O庫的很多實作都以不可重入的方式使用全域資料結構
- 可重入函式體內使用了靜態的資料結構
9.5 常見可重入的情況
- 不使用全域變數或靜態變數
- 不使用用malloc或者new開辟出的空間
- 不呼叫不可重入函式
- 不回傳靜態或全域資料,所有資料都有函式的呼叫者提供
- 使用本地資料,或者通過制作全域資料的本地拷貝來保護全域資料
9.6 可重入與執行緒安全聯系
- 函式是可重入的,那就是執行緒安全的
- 函式是不可重入的,那就不能由多個執行緒使用,有可能引發執行緒安全問題
- 如果一個函式中有全域變數,那么這個函式既不是執行緒安全也不是可重入的
9.7 可重入與執行緒安全區別
- 可重入函式是執行緒安全函式的一種
- 執行緒安全不一定是可重入的,而可重入函式則一定是執行緒安全的,
- 如果將對臨界資源的訪問加上鎖,則這個函式是執行緒安全的,但如果這個重入函式若鎖還未釋放則會產生死鎖,因此是不可重入的
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/279943.html
標籤:其他
上一篇:GUI輸入框監聽事件
下一篇:本人停更告知

