首頁>技術>

什麼是執行緒

執行緒的誕生

執行緒和程序是許多初學者比較難理解的概念,在Linux中執行緒和程序對於核心是一致的,Linux核心是不區分程序和執行緒,其均有獨立的程序控制塊(PCB),只在使用者層面上進行區分。

由於程序是獨立的,且都具有其自身的頁目錄、頁表進而物理頁面等,所以他們具有一致的虛擬地址空間,應用程式訪問相同的地址,其由於對映關係不同而相互之間不會影響。

而Linux執行緒與程序是類似的都有各自獨立的PCB,但是他們訪問著相同的頁目錄、頁表和物理頁面等等,所以多個執行緒之間共享著同一個程序的地址空間,那麼地址內容的修改會相互之間影響。

如果你再抽象的理解可以認為執行緒是多個程序共享一塊地址空間而模擬得到的。

如果足下正在學習C/C++基礎,不妨關注下專欄教程

執行緒與程序

兩個硬核概念 :

執行緒是執行和被排程的最小單元。

程序是資源分配的最小單元。

簡單理解一下上面兩句 : 執行緒是執行和排程的最小單元,如果把沒有多執行緒的程序看成:一個執行緒 + 資源,那麼執行緒就可以認為是程式碼的執行,也是被OS排程的物件,而執行緒不管資源,所以程序就是分配資源的最小單位。

程序VS執行緒 :

多執行緒和多程序都是為了實現任務的併發執行而實現的,從前面的內容看執行緒就是屬於一種輕量級程序。

程序之間是相互獨立的,比如我們使用fork其子程序與父程序程式碼段是共享,不過資料段、堆疊段等非共享,不可相互訪問。

對於程序之間資料獨立,所以需要透過管道、訊息佇列等等機制來完成資訊交換,這樣相對比較浪費時間,且操作麻煩,而執行緒之間是共享的,所以執行緒之間的資料互動並不需要像程序之間那麼複雜就可以完成互動,比如直接訪問變數。

同時在程序切換的過程中需要維護自己的頁表項,即重新對映虛擬地址與物理地址等,而執行緒之間的切換則不需要更新頁表項等,開銷就大大降低,同時彷彿的程序切換對於CPU快取也是不友好的,影響程式執行效率。

執行緒的缺點

當然執行緒也並沒有那麼優秀,由於各程序之間的獨立性,即使有程序掛了不相關的其他程序也能夠繼續執行。而多執行緒之間資料共享,導致執行緒之間的耦合度增加,這樣對於各個執行緒任務的安全帶來了一些挑戰,使用同步互斥機制能夠很好的在安全和效率上做一個權衡,從而發揮執行緒併發的最大功效。由於執行緒的靈活度較大,這也對於程序執行緒開發的程式設計師提高了其對任務的把控能力,如果稍有不慎一個執行緒的掛機就會導致整個程序崩潰!

2執行緒建立

執行緒庫

上一小節小哥跟大家介紹了執行緒以及執行緒與程序相關區別的理解,今天主要是體驗一下執行緒相關的操作和效果。

在Linux中一般分為使用者級執行緒和系統級執行緒,其中使用者級主要是一些執行緒庫,管理過程均由使用者程式來完成,而系統級執行緒由作業系統核心管理。其中我們比較常用的是POSIX執行緒庫,一般也叫pthread,該庫已經把相應的操作封裝好了,我們只需要理解好了執行緒的原理,然後熟悉該庫的API便可以好好使用執行緒。

執行緒庫建立

首先我們來看一下執行緒建立API :

引數說明:

thread : 用於返回執行緒識別符號,傳遞一個pthread變數即可。

attr : 用於設定執行緒的屬性,通常設定為空attr為NULL。

start_routine : 執行緒啟動後要執行函式的起始地址,這個引數我覺得有部分朋友看不太懂,看這段程式碼顏色你應該可以理解void *(*start_routine)(void *)。

arg : 傳給start_routine的引數。

返回值 : 成功返回0,否則失敗返回對應的錯誤碼。

3執行緒建立執行例項

編譯及執行結果

解讀一下

1 ) 首先在進行編譯的時候需要連結-pthread。

2 ) 本例程中由主執行緒建立了兩個子執行緒thread1和thread2,然後看到輸出結果中三個執行緒都執行起來了。

3 ) 使用了一個變數傳遞給三個執行緒進行共享, 從而我們也可以看到各個執行緒中的累計均會影響到其他執行緒的顯示。

4執行緒退出

執行緒正常退出方式

上一小節小哥跟大家一起學習執行緒的建立等知識,新的執行緒透過呼叫start_routine()開始執行,arg作為start_routine()的唯一引數傳遞,新執行緒一旦執行,一般會透過如下四種方式進行終止 :

1)呼叫pthread_exit(3),指定一個退出狀態值,該值對同一個程序中呼叫pthread_join(3)的另一個執行緒可用。

2)從start_routine()返回,這相當於使用return語句中提供的值呼叫pthread_exit(3)。

3)已被取消(參見pthread_cancel(3))。

4)程序中的任何執行緒呼叫exit(3),或者主執行緒執行main()的返回。這將導致程序中所有執行緒的終止。

異常退出

以上提到了執行緒正常的退出方式,這種是可以預見的,比較常用的pthread_exit和直接執行緒內部return,既然有正常退出,就存在非正常退出,比如自身執行錯誤,由於程式執行異常、越界、段錯誤、訪問非法地址,執行非法指令等等,從而不可預知的退出方式。

所以對於正常可預知退出,一個正常的程式都會考慮其退出後資源的釋放問題,比如malloc以後需要free等,當然也有一些程式寫得不咋樣,正常退出沒有釋放的資源,導致記憶體洩漏問題;但對於異常退出如果保證資源的釋放問題就更為重要了。

5執行緒的清除

取消清理處理程式

pthread_cleanup_push() : 將例程推送到清理處理程式棧的頂部。當以後呼叫例程時,將把arg作為它的引數。

也就是說從push到pop之間的程式段中,一旦發生終止動作,比如pthread_exit()或者是異常終止等,注意不包括執行緒中return方式的退出,最後均會執行push引數中指定的routine清理函式,有點類似於C++中的解構函式。

"取消清理處理程式"從堆疊彈出有如下三種情況:

1、當一個執行緒被取消時,所有堆疊的清理處理程式都將彈出並以與它們被推入堆疊的順序相反的順序執行。

2、當一個執行緒透過呼叫pthread_exit(3)終止時,所有的清理處理程式都按照前面描述的那樣執行。(如果執行緒透過執行執行緒start函式的return來終止,那麼清理處理程式不會被呼叫。)

3、當一個執行緒呼叫pthread_cleanup_pop()時,使用一個非零的execute引數,彈出並執行最頂部的清理處理程式。

最後值得注意的是 : 呼叫者必須確保對這些函式的呼叫在同一個函式中成對,並且在相同的詞法巢狀級別上成對。(換句話說,清理處理程式只在執行指定的程式碼段期間建立。)

演示例程

情形1 : 正常情況,pop中execute為0,且在push和pop之間沒有退出,則無影響。

pthread_join解讀

函式的作用是 : 等待執行緒指定的執行緒終止。如果執行緒已經終止,那麼pthread_join()立即返回。執行緒指定的執行緒必須是joinable。

如果retval不是NULL,那麼pthread_join()將目標執行緒的退出狀態(也就是目標執行緒提供給pthread_exit(3)的值)複製到由*retval指向的位置。如果目標執行緒被取消,那麼PTHREAD_CANCELED被放在*retval中。

如果多個執行緒同時嘗試連線同一個執行緒,則結果是未定義的。如果呼叫pthread_join()的執行緒被取消了,那麼目標執行緒仍然是joinable而不是detached。

join的作用

從上面的介紹看Linux執行緒中存在兩種資源回收狀態,一種是joinable,另外一種是detached,其中:

joinable(預設) :當執行緒退出的時候均不會釋放其佔用的資源等等,包括相應的描述符等等,只有透過呼叫pthread_join函式來堵塞當前執行緒直到目標程序結束,最後會把相應的資源回收,否則如果沒有呼叫join那麼執行緒的狀態就類似於執行緒的僵死狀態。

detached: 該狀態其主要是把子執行緒與主執行緒分離,當執行緒退出會自己主動釋放相應資源,這裡不詳細介紹。

pthread_join例項

以上是使用pthread_join來進行等待子執行緒結束的實驗結果,主執行緒一直等待子執行緒執行exit才結束,如果我們不加入pthread_join看看如下結果,其子執行緒根本來不及執行主執行緒就結束了。

補充說明 :pthread_self比較簡單,僅僅是獲得所呼叫執行緒的執行緒標識,跟getpid程序pid是類似的。

15
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 機器學習的幾個概念