程式是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,它是系統進行資源分配和排程的一個獨立單位。例如,使用者執行自己的程式,系統就建立一個程式,併為它分配資源,包括各種表格、記憶體空間、磁碟空間、I/O裝置等,然後該程式被放入到程式的就緒佇列,程式排程程式選中它,為它分配CPU及其他相關資源,該程式就被執行起來。
執行緒是程式的一個實體,是CPU排程和分配的基本單位,執行緒自己基本上不擁有系統資源,只擁有一點在執行中必不可少的資源(如程式計數器、一組暫存器和棧),但是它可以與同屬一個程式的其他的執行緒共享程式所擁有的全部資源。
在沒有實現執行緒的作業系統中,程式既是資源分配的基本單位,又是排程的基本單位,它是系統中併發執行的單元。而在實現了執行緒的作業系統中,程式是資源分配的基本單位,而執行緒是排程的基本單位,是系統中併發執行的單元。
需要注意的是,儘管執行緒與程式很相似,但兩者也存在著很大的不同,區別如下:
1)一個執行緒必定屬於也只能屬於一個程式;而一個程式可以擁有多個執行緒並且至少擁有一個執行緒。
2)屬於一個程式的所有執行緒共享該執行緒的所有資源,包括開啟的檔案、建立的Socket等。不同的程式互相獨立。
3)執行緒又被稱為輕量級程式。程式有程式控制塊,執行緒也有執行緒控制塊。但執行緒控制塊比程式控制塊小得多。執行緒間切換代價小,程式間切換代價大。
4)程式是程式的一次執行,執行緒可以理解為程式中一個程式片段的執行。
5)每個程式都有獨立的記憶體空間,而執行緒共享其所屬程式的記憶體空間。
程式、程式與執行緒的區別見表6-1。
表6-1 程式、程式與執行緒區別
名稱
|
描 述
|
程式
|
一組指令的有序結合,是靜態的指令,是永久存在的。
|
程式
|
具有一定獨立功能的程式關於某個資料集合上的一次執行活動,是系統進行資源分配和排程的一個獨立 單元。程式的存在是暫時的,是一個動態概念。
|
執行緒
|
執行緒的一個實體,是CPU排程和的基本單元,是比程式更小的能獨立執行的基本單元。本身基本上不擁有系統資源,只擁有一點在執行中必不可少的資源(如程式計數器,一組暫存器和棧)。一個執行緒可以建立和撤銷另一個執行緒,同一個程式中的多個執行緒之間可以併發執行。
|
簡而言之,一個程式至少有一個程式,一個程式至少有一個執行緒。
1.定義
程式是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,程式是系統進行資源分配和排程的一個獨立單位
執行緒是程式的一個實體,行的基本單位.執行緒自己基本上不擁有系統資源,只擁有一點在執行中必不可少的資源(如程式計數器,一組暫存器和棧),但是它可與同屬一個程式的其他的執行緒共享程式所擁有的全部資源.是CPU排程和分派的基本單位,它是比程式更小的能獨立運
2.關係
一個執行緒可以建立和撤銷另一個執行緒;同一個程式中的多個執行緒之間可以併發執行.相對程式而言,執行緒是一個更加接近於執行體的概念,它可以與同程式中的其他執行緒共享資料,但擁有自己的棧空間,擁有獨立的執行序列。
3.區別
程式和執行緒的主要差別在於它們是不同的作業系統資源管理方式。程式有獨立的地址空間,一個程式崩潰後,在保護模式下不會對其它程式產生影響,而執行緒只是一個程式中的不同執行路徑。執行緒有自己的堆疊和區域性變數,但執行緒之間沒有單獨的地址空間,一個執行緒死掉就等於整個程式死掉,所以多程式的程式要比多執行緒的程式健壯,但在程式切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變數的併發操作,只能用執行緒,不能用程式。
1) 簡而言之,一個程式至少有一個程式,一個程式至少有一個執行緒.
2) 執行緒的劃分尺度小於程式,使得多執行緒程式的併發性高。
3) 另外,程式在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體,從而極大地提高了程式的執行效率。
4) 執行緒在執行過程中與程式還是有區別的。每個獨立的執行緒有一個程式執行的入口、順序執行序列和程式的出口。但是執行緒不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制。
5) 從邏輯角度來看,多執行緒的意義在於一個應用程式中,有多個執行部分可以同時執行。但作業系統並沒有將多個執行緒看做多個獨立的應用,來實現程式的排程和管理以及資源分配。這就是程式和執行緒的重要區別。
4.優缺點
執行緒和程式在使用上各有優缺點:執行緒執行開銷小,但不利於資源的管理和保護;而程式正相反。同時,執行緒適合於在SMP機器上執行,而程式則可以跨機器遷移。
執行緒退出的條件:下面任意一個都可以。
1.呼叫pthread_exit函式退出。
2.其他執行緒呼叫pthread_cancel取消該執行緒,且該執行緒可被取消。
3.建立執行緒的程式退出或者整個函式結束。
4.當前執行緒程式碼執行完畢。
5.其中的一個執行緒執行exec類函式執行新的程式碼,替換當前程式所有地址空間。
當執行緒中休眠或者死迴圈時候,需要在住程式中呼叫pthread_join等待執行緒結束,死迴圈可以透過另外一個休眠的執行緒來結束,舉例說明,讓LCD顯示攝像頭資料,但是我們中途需要點選觸控式螢幕來退出顯示,影片顯示是一個死迴圈來不停的讀取影片資料,那麼我們就可以建立兩個執行緒,一個負責影片的不停讀取,一個負責獲取觸控式螢幕資料,沒有資料就休眠,當休眠被喚醒後就呼叫pthread_cancel取消死迴圈的執行緒,設計思路基本是這樣。也可以採取程式實現這個操作。
執行緒與程式
為什麼有了程式的概念後,還要再引入執行緒呢?使用多執行緒到底有哪些好處?什麼的系統應該選用多執行緒?我們首先必須回答這些問題。
使用多執行緒的理由之一是和程式相比,它是一種非常"節儉"的多工操作方式。我們知道,在Linux系統下,啟動一個新的程式必須分配給它獨立的地址空間,建立眾多的資料表來維護它的程式碼段、堆疊段和資料段,這是一種"昂貴"的多工工作方式。而執行於一個程式中的多個執行緒,它們彼此之間使用相同的地址空間,共享大部分資料,啟動一個執行緒所花費的空間遠遠小於啟動一個程式所花費的空間,而且,執行緒間彼此切換所需的時間也遠遠小於程式間切換所需要的時間。據統計,總的說來,一個程式的開銷大約是一個執行緒開銷的30倍左右,當然,在具體的系統上,這個資料可能會有較大的區別。
使用多執行緒的理由之二是執行緒間方便的通訊機制。對不同程式來說,它們具有獨立的資料空間,要進行資料的傳遞只能透過通訊的方式進行,這種方式不僅費時,而且很不方便。執行緒則不然,由於同一程式下的執行緒之間共享資料空間,所以一個執行緒的資料可以直接為其它執行緒所用,這不僅快捷,而且方便。當然,資料的共享也帶來其他一些問題,有的變數不能同時被兩個執行緒所修改,有的子程式中宣告為static的資料更有可能給多執行緒程式帶來災難性的打擊,這些正是編寫多執行緒程式時最需要注意的地方。
除了以上所說的優點外,不和程式比較,多執行緒程式作為一種多工、併發的工作方式,當然有以下的優點:
1) 提高應用程式響應。這對圖形介面的程式尤其有意義,當一個操作耗時很長時,整個系統都會等待這個操作,此時程式不會響應鍵盤、滑鼠、選單的操作,而使用多執行緒技術,將耗時長的操作(time consuming)置於一個新的執行緒,可以避免這種尷尬的情況。
2) 使多CPU系統更加有效。作業系統會保證當執行緒數不大於CPU數目時,不同的執行緒執行於不同的CPU上。
3) 改善程式結構。一個既長又複雜的程式可以考慮分為多個執行緒,成為幾個獨立或半獨立的執行部分,這樣的程式會利於理解和修改。
一、執行緒標識
-
執行緒有ID, 但不是系統唯一, 而是程式環境中唯一有效.
-
執行緒的控制程式碼是pthread_t型別, 該型別不能作為整數處理, 而是一個結構.
下面介紹兩個函式:
-
標頭檔案:
-
原型: int pthread_equal(pthread_t tid1, pthread_t tid2);
-
返回值: 相等返回非0, 不相等返回0.
-
說明: 比較兩個執行緒ID是否相等.
-
標頭檔案:
-
原型: pthread_t pthread_self();
-
返回值: 返回撥用執行緒的執行緒ID.
二、執行緒建立
在執行中建立一個執行緒, 可以為該執行緒分配它需要做的工作(執行緒執行函式), 該執行緒共享程式的資源. 建立執行緒的函式pthread_create()
-
標頭檔案:
-
原型: int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(start_rtn)(void), void *restrict arg);
-
返回值: 成功則返回0, 否則返回錯誤編號.
-
引數:
-
tidp: 指向新建立執行緒ID的變數, 作為函式的輸出.
-
attr: 用於定製各種不同的執行緒屬性, NULL為預設屬性(見下).
-
start_rtn: 函式指標, 為執行緒開始執行的函式名.該函式可以返回一個void *型別的返回值,而這個返回值也可以是其他型別,並由 pthread_join()獲取
-
arg: 函式的唯一無型別(void)指標引數, 如要傳多個引數, 可以用結構封裝.
linux下多執行緒程式的編譯方法:
因為pthread的庫不是linux系統的庫,所以在進行編譯的時候要加上 -lpthread
# gcc filename -lpthread //預設情況下gcc使用c庫,要使用額外的庫要這樣選擇使用的庫
三、執行緒屬性
pthread_create()中的attr引數是一個結構指標,結構中的元素分別對應著新執行緒的執行屬性,主要包括以下幾項:
__detachstate,表示新執行緒是否與程式中其他執行緒脫離同步,如果置位則新執行緒不能用pthread_join()來同步,且在退出時自行釋放所佔用的資源。預設為PTHREAD_CREATE_JOINABLE狀態。這個屬性也可以線上程建立並執行以後用pthread_detach()來設定,而一旦設定為PTHREAD_CREATE_DETACH狀態(不論是建立時設定還是執行時設定)則不能再恢復到 PTHREAD_CREATE_JOINABLE狀態。
__schedpolicy,表示新執行緒的排程策略,主要包括SCHED_OTHER(正常、非實時)、SCHED_RR(實時、輪轉法)和 SCHED_FIFO(實時、先入先出)三種,預設為SCHED_OTHER,後兩種排程策略僅對超級使用者有效。執行時可以用過 pthread_setschedparam()來改變。
__schedparam,一個struct sched_param結構,目前僅有一個sched_priority整型變數表示執行緒的執行優先順序。這個引數僅當排程策略為實時(即SCHED_RR或SCHED_FIFO)時才有效,並可以在執行時透過pthread_setschedparam()函式來改變,預設為0。
__inheritsched,有兩種值可供選擇:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新執行緒使用顯式指定排程策略和排程引數(即attr中的值),而後者表示繼承呼叫者執行緒的值。預設為PTHREAD_EXPLICIT_SCHED。
__scope,表示執行緒間競爭CPU的範圍,也就是說執行緒優先順序的有效範圍。POSIX的標準中定義了兩個值: PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示與系統中所有執行緒一起競爭CPU時間,後者表示僅與同程式中的執行緒競爭CPU。目前LinuxThreads僅實現了PTHREAD_SCOPE_SYSTEM一值。
pthread_attr_t結構中還有一些值,為了設定這些屬性,POSIX定義了一系列屬性設定函式,包括pthread_attr_init()、 pthread_attr_destroy()和與各個屬性相關的pthread_attr_get(),pthread_attr_set()函式。
pthread_create()中,第二個引數(pthread_attr_t)為將要建立的thread屬性。通常情況下配置為NULL,使用預設設定就可以了。但瞭解這些屬性,有利於更好的理解thread.
屬性物件(pthread_attr_t)是不透明的,而且不能透過賦值直接進行修改。系統提供了一組函式,用於初始化、配置和銷燬每種物件型別。
建立屬性:
int pthread_attr_init(pthread_attr_t *attr);
建立的屬性設定為預設設定。
銷燬屬性:
int pthread_attr_destroy(pthread_attr_t *attr);
一:設定分離狀態:
執行緒的分離狀態有2種:PTHREAD_CREATE_JOINABLE(非分離狀態), PTHREAD_CREATE_DETACHED(分離狀態)
分離狀態含義如下:
如果使用 PTHREAD_CREATE_JOINABLE 建立非分離執行緒,則假設應用程式將等待執行緒完成。也就是說,程式將對執行緒執行 pthread_join。 非分離執行緒在終止後,必須要有一個執行緒用 join 來等待它。否則,不會釋放該執行緒的資源以供新執行緒使用,而這通常會導致記憶體洩漏。因此,如果不希望執行緒被等待,請將該執行緒作為分離執行緒來建立。
如果使用 PTHREAD_CREATE_DETACHED 建立分離thread,則表明此thread在退出時會自動回收資源和thread ID.
Sam之前很喜歡使用分離thread. 但現在慢慢使用中覺得這樣是個不好的習慣。因為分離thread有個問題:主程式退出時,很難確認子thread已經退出。只好使用全域性變數來標明子thread已經正常退出了。
另外:不管建立分離還是非分離的thread.在子thread全部退出之前退出主程式都是很有風險的。如果主thread選擇return,或者呼叫exit()退出,則所有thread都會被kill掉。這樣很容易出錯。Sam上次出的問題其實就是這個。但如果主thread只是呼叫pthread_exit().則僅主執行緒本身終止。程式及程式內的其他執行緒將繼續存在。所有執行緒都已終止時,程式也將終止。
intpthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
得到當前和分離狀態和設定當前的分離狀態。
二:設定棧溢位保護區大小:
棧溢位概念:
· 溢位保護可能會導致系統資源浪費。如果應用程式建立大量執行緒,並且已知這些執行緒永遠不會溢位其棧,則可以關閉溢位保護區。透過關閉溢位保護區,可以節省系統資源。
· 執行緒在棧上分配大型資料結構時,可能需要較大的溢位保護區來檢測棧溢位。
int pthread_attr_getguardsize(const pthread_attr_t *restrictattr,size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize);
設定和得到棧溢位保護區。如果guardsize設為0。則表示不設定棧溢位保護區。guardsize 的值向上舍入為PAGESIZE 的倍數。
三:設定thread競用範圍:
競用範圍(PTHREAD_SCOPE_SYSTEM 或 PTHREAD_SCOPE_PROCESS)指 使用 PTHREAD_SCOPE_SYSTEM 時,此執行緒將與系統中的所有執行緒進行競爭。使用 PTHREAD_SCOPE_PROCESS 時,此執行緒將與程式中的其他執行緒進行競爭。
int pthread_attr_getscope(const pthread_attr_t *restrict attr,int*restrict contentionscope);
int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);
四:設定執行緒並行級別:
int pthread_getconcurrency(void);
int pthread_setconcurrency(int new_level);
Sam不理解這個意思。
五:設定排程策略:
POSIX 標準指定 SCHED_FIFO(先入先出)、SCHED_RR(迴圈)或 SCHED_OTHER(實現定義的方法)的排程策略屬性。
· SCHED_FIFO
如果呼叫程式具有有效的使用者 ID 0,則爭用範圍為系統 (PTHREAD_SCOPE_SYSTEM) 的先入先出執行緒屬於實時 (RT) 排程類。如果這些執行緒未被優先順序更高的執行緒搶佔,則會繼續處理該執行緒,直到該執行緒放棄或阻塞為止。對於具有程式爭用範圍 (PTHREAD_SCOPE_PROCESS)) 的執行緒或其呼叫程式沒有有效使用者 ID 0 的執行緒,請使用 SCHED_FIFO。SCHED_FIFO 基於 TS 排程類。
· SCHED_RR
如果呼叫程式具有有效的使用者 ID 0,則爭用範圍為系統 (PTHREAD_SCOPE_SYSTEM)) 的迴圈執行緒屬於實時 (RT) 排程類。如果這些執行緒未被優先順序更高的執行緒搶佔,並且這些執行緒沒有放棄或阻塞,則在系統確定的時間段內將一直執行這些執行緒。對於具有程式爭用範圍 (PTHREAD_SCOPE_PROCESS) 的執行緒,請使用SCHED_RR(基於 TS 排程類)。此外,這些執行緒的呼叫程式沒有有效的使用者ID 0。
SCHED_FIFO 是基於佇列的排程程式,對於每個優先順序都會使用不同的佇列。SCHED_RR 與 FIFO 相似,不同的是前者的每個執行緒都有一個執行時間配額。
int pthread_attr_getschedpolicy(const pthread_attr_t *restrictattr,int *restrict policy);
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
六:設定優先順序:
int pthread_attr_getschedparam(const pthread_attr_t *restrictattr,struct sched_param *restrict param);
int pthread_attr_setschedparam(pthread_attr_t *restrict attr,
conststruct sched_param *restrict param);
比較複雜,Sam沒去研究。
七:設定棧大小:
當建立一個thread時,會給它分配一個棧空間,執行緒棧是從頁邊界開始的。任何指定的大小都被向上舍入到下一個頁邊界。不具備訪問許可權的頁將被附加到棧的溢位端(第二項設定中設定)。
指定棧時,還應使用 PTHREAD_CREATE_JOINABLE 建立執行緒。在該執行緒的 pthread_join() 呼叫返回之前,不會釋放該棧。在該執行緒終止之前,不會釋放該執行緒的棧。瞭解這類執行緒是否已終止的唯一可靠方式是使用pthread_join。
一般情況下,不需要為執行緒分配棧空間。系統會為每個執行緒的棧分配指定大小的虛擬記憶體。
#ulimit -a可以看到這個預設大小
四、執行緒終止
如果程式中的任一執行緒呼叫了exit,_Exit或者_exit,那麼整個程式就會終止。與此類似,如果訊號的預設動作是終止程式,那麼,把該訊號傳送到執行緒會終止整個程式。
單個執行緒可以透過下列三種方式退出,在不終止整個程式的情況下停止它的控制流。
(1):從啟動例程中返回,返回值是執行緒的退出碼
(2):執行緒可以被同一程式中的其他執行緒取消
(3):執行緒呼叫pthread_exit()
pthread_exit函式:
-
原型: void pthread_exit(void *rval_ptr);
-
標頭檔案:
-
引數: rval_ptr是一個無型別指標, 指向執行緒的返回值儲存變數.
pthread_join函式:
-
原型: int pthread_join(pthread_t thread, void **rval_ptr);
-
標頭檔案:
-
返回值: 成功則返回0, 否則返回錯誤編號.
-
引數:
-
thread: 執行緒ID.
-
rval_ptr: 指向返回值的指標(返回值也是個指標).
-
說明:
-
呼叫執行緒將一直阻塞, 直到指定的執行緒呼叫pthread_exit, 從啟動例程返回或被取消.
-
如果執行緒從它的啟動例程返回, rval_ptr包含返回碼.
-
如果執行緒被取消, 由rval_ptr指定的記憶體單元置為: PTHREAD_CANCELED.
-
如果對返回值不關心, 可把rval_ptr設為NULL.
執行緒取消的定義:
一般情況下,執行緒在其主體函式退出的時候會自動終止,但同時也可以因為接收到另一個執行緒發來的終止(取消)請求而強制終止。
執行緒取消的語義:
執行緒取消的方法是向目標執行緒發Cancel訊號,但如何處理Cancel訊號則由目標執行緒自己決定,或者忽略、或者立即終止、或者繼續執行至Cancelation-point(取消點),由不同的Cancelation狀態決定。
執行緒接收到CANCEL訊號的預設處理(即pthread_create()建立執行緒的預設狀態)是繼續執行至取消點,也就是說設定一個CANCELED狀態,執行緒繼續執行,只有執行至Cancelation-point的時候才會退出。
取消點定義:
根據POSIX標準,pthread_join()、pthread_testcancel()、pthread_cond_wait()、 pthread_cond_timedwait()、sem_wait()、sigwait()等函式以及read()、write()等會引起阻塞的系統呼叫都是Cancelation-point,而其他pthread函式都不會引起Cancelation動作。但是pthread_cancel的手冊頁聲稱,由於LinuxThread庫與C庫結合得不好,因而目前C庫函式都不是Cancelation-point;但CANCEL訊號會使執行緒(http://blog.csdn.net/shanzhizi)從阻塞的系統呼叫中退出,並置EINTR錯誤碼,因此可以在需要作為Cancelation-point的系統呼叫前後呼叫 pthread_testcancel(),從而達到POSIX標準所要求的目標,即如下程式碼段:
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();
程式設計方面的考慮:
如果執行緒處於無限迴圈中,且迴圈體內沒有執行至取消點的必然路徑,則執行緒無法由外部其他執行緒的取消請求而終止。因此在這樣的迴圈體的必經路徑上應該加入pthread_testcancel()呼叫。即如下程式碼段:
While(1)
{
………
pthread_testcancel();
}
與執行緒取消相關的pthread函式:
intpthread_cancel(pthread_t thread):執行緒可以透過呼叫pthread_cancel函式來請求取消同一程式中的其他程式。
傳送終止訊號給thread執行緒,如果成功則返回0,否則為非0值。傳送成功並不意味著thread會終止。注意pthread_cancel並不等待執行緒終止,它僅僅提出請求。
int pthread_setcancelstate(int state, int*oldstate):
設定本執行緒對Cancel訊號的反應,state有兩種值:PTHREAD_CANCEL_ENABLE(預設)和 PTHREAD_CANCEL_DISABLE,分別表示收到訊號後設為CANCLED狀態和忽略CANCEL訊號繼續執行;old_state如果不為 NULL則存入原來的Cancel狀態以便恢復。
int pthread_setcanceltype(int type, int*oldtype)
設定本執行緒取消動作的執行時機,type由兩種取值:PTHREAD_CANCEL_DEFFERED和 PTHREAD_CANCEL_ASYCHRONOUS,僅當Cancel狀態為Enable時有效,分別表示收到訊號後繼續執行至下一個取消點再退出和立即執行取消動作(退出);oldtype如果不為NULL則存入原來的取消動作型別值。
void pthread_testcancel(void)
檢查本執行緒是否處於Canceld狀態,如果是,則進行取消動作,否則直接返回。
http://blog.csdn.net/shanzhizi
執行緒可以安排它退出時需要呼叫的函式,這與程式可以用atexit函式安排程式退出時需要呼叫的函式是類似的。執行緒可以建立多個清理處理程式。處理程式記錄在棧中,也就是說它們的執行順序與它們註冊時的順序相反。
#include
void pthread_cleanup_push(void(*rtn)(void*),void *arg);
void pthread_cleanup_pop(int execute);
當執行緒執行以下動作時呼叫清理函式,呼叫引數為arg,清理函式的呼叫順序用pthread_cleanup_push來安排。
呼叫pthread_exit時
響應取消請求時
用非0的execute引數呼叫pthread_cleanup_pop時。
如果執行緒是透過從它的啟動例程中返回而終止的話,那麼它的清理處理程式就不會被呼叫,還要注意清理處理程式是按照與它們安裝時相反的順序被呼叫的。
int pthread_detach(pthread_t tid);
可以用於使執行緒進入分離狀態。
執行緒屬性識別符號:pthread_attr_t 包含在 pthread.h 標頭檔案中。
-
-
typedef struct
-
{
-
int etachstate;
-
int schedpolicy;
-
structsched_param schedparam;
-
int inheritsched;
-
int scope;
-
size_t guardsize;
-
int stackaddr_set;
-
void* stackaddr;
-
size_t stacksize;
-
}pthread_attr_t;
屬性值不能直接設定,須使用相關函式進行操作,初始化的函式為pthread_attr_init,這個函式必須在pthread_create函式之前呼叫。之後須用pthread_attr_destroy函式來釋放資源。執行緒屬性主要包括如下屬性:作用域(scope)、棧尺寸(stack size)、棧地址(stack address)、優先順序(priority)、分離的狀態(detached state)、排程策略和引數(scheduling policy and parameters)。預設的屬性為非繫結、非分離、預設1M的堆疊、與父程式同樣級別的優先順序。
一、執行緒的作用域(scope)
作用域屬性描述特定執行緒將與哪些執行緒競爭資源。執行緒可以在兩種競爭域內競爭資源:
-
程式域(process scope):與同一程式內的其他執行緒。
-
系統域(system scope):與系統中的所有執行緒。一個具有系統域的執行緒將與整個系統中所有具有系統域的執行緒按照優先順序競爭處理器資源,進行排程。
-
Solaris系統,實際上,從 Solaris 9 發行版開始,系統就不再區分這兩個範圍。
二、執行緒的繫結狀態(binding state)
輕程式(LWP:Light Weight Process)關於執行緒的繫結,牽涉到另外一個概念:輕程式(LWP:Light Weight Process):輕程式可以理解為核心執行緒,它位於使用者層和系統層之間。系統對執行緒資源的分配、對執行緒的控制是透過輕程式來實現的,一個輕程式可以控制一個或多個執行緒。
-
非繫結狀態
預設狀況下,啟動多少輕程式、哪些輕程式來控制哪些執行緒是由系統來控制的,這種狀況即稱為非繫結的。
-
繫結狀態
繫結狀況下,則顧名思義,即某個執行緒固定的"綁"在一個輕程式之上。被繫結的執行緒具有較高的響應速度,這是因為CPU時間片的排程是面向輕程式的,繫結的執行緒可以保證在需要的時候它總有一個輕程式可用。透過設定被繫結的輕程式的優先順序和排程級可以使得繫結的執行緒滿足諸如實時反應之類的要求。
三、執行緒的分離狀態(detached state)
-
執行緒的分離狀態決定一個執行緒以什麼樣的方式來終止自己。
-
非分離狀態
執行緒的預設屬性是非分離狀態,這種情況下,原有的執行緒等待建立的執行緒結束。只有當pthread_join()函式返回時,建立的執行緒才算終止,才能釋放自己佔用的系統資源。
-
分離狀態
分離執行緒沒有被其他的執行緒所等待,自己執行結束了,執行緒也就終止了,馬上釋放系統資源。應該根據自己的需要,選擇適當的分離狀態。
-
執行緒分離狀態的函式:pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。
第二個引數可選為PTHREAD_CREATE_DETACHED(分離執行緒)和 PTHREAD _CREATE_JOINABLE(非分離執行緒)。
這裡要注意的一點是,如果設定一個執行緒為分離執行緒,而這個執行緒執行又非常快,它很可能在pthread_create函式返回之前就終止了,它終止以後就可能將執行緒號和系統資源移交給其他的執行緒使用,這樣呼叫pthread_create的執行緒就得到了錯誤的執行緒號。要避免這種情況可以採取一定的同步措施,最簡單的方法之一是可以在被建立的執行緒裡呼叫pthread_cond_timewait函式,讓這個執行緒等待一會兒,留出足夠的時間讓函式pthread_create返回。設定一段等待時間,是在多執行緒程式設計裡常用的方法。但是注意不要使用諸如wait()之類的函式,它們是使整個程式睡眠,並不能解決執行緒同步的問題。
四、執行緒的優先順序(priority)
-
新執行緒的優先順序為預設為0。
-
新執行緒不繼承父執行緒排程優先順序(PTHREAD_EXPLICIT_SCHED)
-
僅當排程策略為實時(即SCHED_RR或SCHED_FIFO)時才有效,並可以在執行時透過pthread_setschedparam()函式來改變,預設為0。
五、執行緒的棧地址(stack address)
-
POSIX.1定義了兩個常量_POSIX_THREAD_ATTR_STACKADDR 和_POSIX_THREAD_ATTR_STACKSIZE檢測系統是否支援棧屬性。
-
也可以給sysconf函式傳遞_SC_THREAD_ATTR_STACKADDR或 _SC_THREAD_ATTR_STACKSIZE來進行檢測。
-
當程式棧地址空間不夠用時,指定新建執行緒使用由malloc分配的空間作為自己的棧空間。透過pthread_attr_setstackaddr和pthread_attr_getstackaddr兩個函式分別設定和獲取執行緒的棧地址。傳給pthread_attr_setstackaddr函式的地址是緩衝區的低地址(不一定是棧的開始地址,棧可能從高地址往低地址增長)。
六、執行緒的棧大小(stack size)
-
當系統中有很多執行緒時,可能需要減小每個執行緒棧的預設大小,防止程式的地址空間不夠用
-
當執行緒呼叫的函式會分配很大的區域性變數或者函式呼叫層次很深時,可能需要增大執行緒棧的預設大小。
-
函式pthread_attr_getstacksize和 pthread_attr_setstacksize提供設定。
七、執行緒的棧保護區大小(stack guard size)
-
線上程棧頂留出一段空間,防止棧溢位。
-
當棧指標進入這段保護區時,系統會發出錯誤,通常是傳送訊號給執行緒。
-
該屬性預設值是PAGESIZE大小,該屬性被設定時,系統會自動將該屬性大小補齊為頁大小的整數倍。
-
當改變棧地址屬性時,棧保護區大小通常清零。
八、執行緒的排程策略(schedpolicy)
POSIX標準指定了三種排程策略:先入先出策略 (SCHED_FIFO)、迴圈策略 (SCHED_RR) 和自定義策略 (SCHED_OTHER)。SCHED_FIFO 是基於佇列的排程程式,對於每個優先順序都會使用不同的佇列。SCHED_RR 與 FIFO 相似,不同的是前者的每個執行緒都有一個執行時間配額。SCHED_FIFO 和 SCHED_RR 是對 POSIX Realtime 的擴充套件。SCHED_OTHER 是預設的排程策略。
-
新執行緒預設使用 SCHED_OTHER 排程策略。執行緒一旦開始執行,直到被搶佔或者直到執行緒阻塞或停止為止。
-
SCHED_FIFO
如果呼叫程式具有有效的使用者 ID 0,則爭用範圍為系統 (PTHREAD_SCOPE_SYSTEM) 的先入先出執行緒屬於實時 (RT) 排程類。如果這些執行緒未被優先順序更高的執行緒搶佔,則會繼續處理該執行緒,直到該執行緒放棄或阻塞為止。對於具有程式爭用範圍 (PTHREAD_SCOPE_PROCESS)) 的執行緒或其呼叫程式沒有有效使用者 ID 0 的執行緒,請使用 SCHED_FIFO,SCHED_FIFO 基於 TS 排程類。
-
SCHED_RR
如果呼叫程式具有有效的使用者 ID 0,則爭用範圍為系統 (PTHREAD_SCOPE_SYSTEM)) 的迴圈執行緒屬於實時 (RT) 排程類。如果這些執行緒未被優先順序更高的執行緒搶佔,並且這些執行緒沒有放棄或阻塞,則在系統確定的時間段內將一直執行這些執行緒。對於具有程式爭用範圍 (PTHREAD_SCOPE_PROCESS) 的執行緒,請使用 SCHED_RR(基於 TS 排程類)。此外,這些執行緒的呼叫程式沒有有效的使用者 ID 0。
九、執行緒並行級別(concurrency)
應用程式使用 pthread_setconcurrency() 通知系統其所需的併發級別。
首先從OS設計原理上闡明三種執行緒:核心執行緒、輕量級程式、使用者執行緒
核心執行緒
核心執行緒就是核心的分身,一個分身可以處理一件特定事情。這在處理非同步事件如非同步IO時特別有用。核心執行緒的使用是廉價的,唯一使用的資源就是核心棧和上下文切換時儲存暫存器的空間。支援多執行緒的核心叫做多執行緒核心(Multi-Threads kernel )。
輕量級程式
輕量級執行緒(LWP)是一種由核心支援的使用者執行緒。它是基於核心執行緒的高階抽象,因此只有先支援核心執行緒,才能有LWP。每一個程式有一個或多個LWPs,每個LWP由一個核心執行緒支援。這種模型實際上就是恐龍書上所提到的一對一執行緒模型。在這種實現的作業系統中,LWP就是使用者執行緒。
由於每個LWP都與一個特定的核心執行緒關聯,因此每個LWP都是一個獨立的執行緒排程單元。即使有一個LWP在系統呼叫中阻塞,也不會影響整個程式的執行。
輕量級程式具有侷限性。首先,大多數LWP的操作,如建立、析構以及同步,都需要進行系統呼叫。系統呼叫的代價相對較高:需要在user mode和kernel mode中切換。其次,每個LWP都需要有一個核心執行緒支援,因此LWP要消耗核心資源(核心執行緒的棧空間)。因此一個系統不能支援大量的LWP。
注:
LWP 的術語是借自於 SVR4/MP 和 Solaris 2.x 。有些系統將 LWP 稱為虛擬處理器。而將之稱為輕量級程式的原因可能是:在核心執行緒的支援下, LWP 是獨立的排程單元,就像普通的程式一樣。所以 LWP 的最大特點還是每個 LWP 都有一個核心執行緒支援。
使用者執行緒
LWP雖然本質上屬於使用者執行緒,但LWP執行緒庫是建立在核心之上的,LWP的許多操作都要進行系統呼叫,因此效率不高。而這裡的使用者執行緒指的是完全建立在使用者空間的執行緒庫,使用者執行緒的建立,同步,銷燬,排程完全在使用者空間完成,不需要核心的幫助。因此這種執行緒的操作是極其快速的且低消耗的。
上圖是最初的一個使用者執行緒模型,從中可以看出,程式中包含執行緒,使用者執行緒在使用者空間中實現,核心並沒有直接對使用者執行緒程式排程,核心的排程物件和傳統程式一樣,還是程式本身,核心並不知道使用者執行緒的存在。使用者執行緒之間的排程由在使用者空間實現的執行緒庫實現。
這種模型對應著恐龍書中提到的多對一執行緒模型,其缺點是一個使用者執行緒如果阻塞在系統呼叫中,則整個程式都將會阻塞。
加強版的使用者執行緒——使用者執行緒+LWP
這種模型對應著恐龍書中多對多模型。使用者執行緒庫還是完全建立在使用者空間中,因此使用者執行緒的操作還是很廉價,因此可以建立任意多需要的使用者執行緒。作業系統提供了 LWP 作為使用者執行緒和核心執行緒之間的橋樑。 LWP 還是和前面提到的一樣,具有核心執行緒支援,是核心的排程單元,並且使用者執行緒的系統呼叫要透過 LWP ,因此程式中某個使用者執行緒的阻塞不會影響整個程式的執行。使用者執行緒庫將建立的使用者執行緒關聯到 LWP 上, LWP 與使用者執行緒的數量不一定一致。當核心排程到某個 LWP 上時,此時與該 LWP 關聯的使用者執行緒就被執行。
很多文獻中都認為輕量級程式就是執行緒,實際上這種說法並不完全正確,從前面的分析中可以看到,只有在使用者執行緒完全由輕量級程式構成時,才可以說輕量級程式就是執行緒。
LinuxThreads
所實現的就是基於核心輕量級程式的"一對一"執行緒模型,一個執行緒實體對應一個核心輕量級程式,而執行緒之間的管理在核外函式庫(我們常用的pthread庫)中實現。 一直以來, linux核心並沒有執行緒的概念. 每一個執行實體都是一個task_struct結構, 通常稱之為程式.
程式是一個執行單元, 維護著執行相關的動態資源. 同時, 它又引用著程式所需的靜態資源.透過系統呼叫clone建立子程式時, 可以有選擇性地讓子程式共享父程式所引用的資源. 這樣的子程式通常稱為輕量級程式,如上文所述,又叫核心執行緒。
[插曲]說下fork和vfork的區別
fork時,子程式是父程式的一個複製。子程式從父程式那得到了資料段和堆疊段,但不是與父程式共享而是單獨分配記憶體。然而這裡的非共享最初狀態是共享的,linux下使用了寫時複製技術,剛開始共享父程式的資料段,在寫資料段的時候才進行復制,以fork為例,最終共享的資源就是task_struct、系統空間堆疊(copy_thread)、頁面表等。
vfork時,因為實現為子程式先執行,所以是不複製(沒必要)父進的虛存空間,也就是使用者空間堆疊,clone(clone_vfork|clone_vm|sigchld,0),指明的引數是不會複製,註定共享的。因為現在的fork都是寫實複製,所以vfork的優勢便不明顯了——只是不用向vfork那樣複製頁表。另外,核心都是優先讓子程式先執行,考慮到排程問題,fork不保證;但vfork可保證這點 2.4核心 do_dork是fork/vfork/clone系統呼叫的共同程式碼,其核心流程如下:
1) 預設對所有資源進行共享暫不復制;
2) 如果flags未指定共享(相應位為0),則進行深層次複製。包括,file,fs,sighand,mm。
以CLONE_FILES為例,對fork而言為1,也就是必須複製兩份files,這樣父子程式才有獨立上下文(各自獨立lseek不影響,但是檔案指標肯定還是指向一個);但是對於vfork,這個標誌為1,也就是父子程式共享檔案上下文(注意這裡不是共享檔案指標,連上下文都共享!也就是子程式lseek會改變父程式讀寫位置),這豈不亂套(類似還有vfork的CLONE_VM標誌)!別擔心,do_fork中會保證vfork時候,自程式先執行完!
特殊說下,mm資源即使是非共享的,即CLONE_VM=1(fork如此),也不馬上覆制,而是複製頁面表後,把也表項設定為防寫,這樣無論誰寫,屆時都會再複製一份出來,這才完成的資源的獨立——對fork而言。
3) 複製系統堆疊(區別於使用者空間VM)
使用者態執行緒由pthread庫實現,使用pthread以後, 在使用者看來, 每一個task_struct就對應一個執行緒, 而一組執行緒以及它們所共同引用的一組資源就是一個程式. 但是, 一組執行緒並不僅僅是引用同一組資源就夠了, 它們還必須被視為一個整體.
POSIX執行緒實現基於如下要求:
1, 檢視程式列表的時候, 相關的一組task_struct應當被展現為列表中的一個節點;
2, 傳送給這個"程式"的訊號(對應kill系統呼叫), 將被對應的這一組task_struct所共享, 並且被其中的任意一個"執行緒"處理;
3, 傳送給某個"執行緒"的訊號(對應pthread_kill), 將只被對應的一個task_struct接收, 並且由它自己來處理;
4, 當"程式"被停止或繼續時(對應SIGSTOP/SIGCONT訊號), 對應的這一組task_struct狀態將改變;
5, 當"程式"收到一個致命訊號(比如由於段錯誤收到SIGSEGV訊號), 對應的這一組task_struct將全部退出;
6, 以上可能不全;
在linux 2.6以前, pthread執行緒庫對應的實現是一個名叫linuxthreads的lib. linuxthreads利用前面提到的輕量級程式來實現執行緒, 但是對於POSIX提出的那些要求, linuxthreads 除了第5點以外, 都沒有實現(實際上是無能為力):
1, 如果執行了A程式, A程式建立了10個執行緒, 那麼在shell下執行ps命令時將看到11個A程式, 而不是1個(注意, 也不是10個, 下面會解釋);
2, 不管是kill還是pthread_kill, 訊號只能被一個對應的執行緒所接收;
3, SIGSTOP/SIGCONT訊號只對一個執行緒起作用;
還好linuxthreads實現了第5點, 我認為這一點是最重要的. 如果某個執行緒"掛"了, 整個程式還在若無其事地執行著, 可能會出現很多的不一致狀態. 程式將不是一個整體, 而執行緒也不能稱為執行緒. 或許這也是為什麼linuxthreads雖然與POSIX的要求差距甚遠, 卻能夠存在, 並且還被使用了好幾年的原因吧。是, linuxthreads為了實現這個"第5點", 還是付出了很多代價, 並且創造了linuxthreads本身的一大效能瓶頸.
接下來要說說, 為什麼A程式建立了10個執行緒, 但是ps時卻會出現11個A程式了. 因為linuxthreads自動建立了一個管理執行緒. 上面提到的"第5點"就是靠管理執行緒來實現的.當程式開始執行時, 並沒有管理執行緒存在(因為儘管程式已經連結了pthread庫, 但是未必會使用多執行緒). 程式第一次呼叫pthread_create時, linuxthreads發現管理執行緒不存在, 於是建立這個管理執行緒. 這個管理執行緒是程式中的第一個執行緒(主執行緒)的兒子.
然後在pthread_create中, 會透過pipe向管理執行緒傳送一個命令, 告訴它建立執行緒. 即是說, 除主執行緒外, 所有的執行緒都是由管理執行緒來建立的, 管理執行緒是它們的父親.於是, 當任何一個子執行緒退出時, 管理執行緒將收到SIGUSER1訊號(這是在透過clone建立子執行緒時指定的). 管理執行緒在對應的sig_handler中會判斷子執行緒是否正常退出, 如果不是, 則殺死所有執行緒, 然後自殺.
那麼, 主執行緒怎麼辦呢? 主執行緒是管理執行緒的父親, 其退出時並不會給管理執行緒發訊號. 於是, 在管理執行緒的主迴圈中透過getppid檢查父程式的ID號, 如果ID號是1, 說明父親已經退出, 並把自己託管給了init程式(1號程式). 這時候, 管理執行緒也會殺掉所有子執行緒, 然後自殺. 那麼, 如果主執行緒是呼叫pthread_exit主動退出的呢? 按照posix的標準,這種情況下其他子執行緒是應該繼續執行的. 於是, 在linuxthreads中, 主執行緒呼叫pthread_exit以後並不會真正退出, 而是會在pthread_exit函式中阻塞等待所有子執行緒都退出了, pthread_exit才會讓主執行緒退出. (在這個等等過程中, 主執行緒一直處於睡眠狀態.)
可見, 執行緒的建立與銷燬都是透過管理執行緒來完成的, 於是管理執行緒就成了linuxthreads的一個效能瓶頸. 建立與銷燬需要一次程式間通訊, 一次上下文切換之後才能被管理執行緒執行, 並且多個請求會被管理執行緒序列地執行.
NPTL(Native POSIX Threading Library)
到了linux 2.6, glibc中有了一種新的pthread執行緒庫NPTL. NPTL實現了前面提到的POSIX的全部5點要求. 但是, 實際上, 與其說是NPTL實現了, 不如說是linux核心實現了.
在linux 2.6中, 核心有了執行緒組的概念, task_struct結構中增加了一個tgid(thread group id)欄位. 如果這個task是一個"主執行緒", 則它的tgid等於pid, 否則tgid等於程式的pid(即主執行緒的pid),此外,每個執行緒有自己的pid。在clone系統呼叫中, 傳遞CLONE_THREAD引數就可以把新程式的tgid設定為父程式的tgid(否則新程式的tgid會設為其自身的pid).類似的XXid在task_struct中還有兩個:task->signal->pgid儲存程式組的打頭程式的pid、task->signal->session儲存會話打頭程式的pid。透過這兩個id來關聯程式組和會話。
有了tgid, 核心或相關的shell程式就知道某個tast_struct是代表一個程式還是代表一個執行緒, 也就知道在什麼時候該展現它們, 什麼時候不該展現(比如在ps的時候, 執行緒就不要展現了).而getpid(獲取程式ID)系統呼叫返回的也是tast_struct中的tgid, 而tast_struct中的pid則由gettid系統呼叫來返回.在執行ps命令的時候不展現子執行緒,也是有一些問題的。比如程式a.out執行時,建立了一個執行緒。假設主執行緒的pid是10001、子執行緒是10002(它們的tgid都是10001)。這時如果你kill 10002,是可以把10001和10002這兩個執行緒一起殺死的,儘管執行ps命令的時候根本看不到10002這個程式。如果你不知道linux執行緒背後的故事,肯定會覺得遇到靈異事件了。
為了應付"傳送給程式的訊號"和"傳送給執行緒的訊號", task_struct裡面維護了兩套signal_pending, 一套是執行緒組共享的, 一套是執行緒獨有的.透過kill傳送的訊號被放線上程組共享的signal_pending中, 可以由任意一個執行緒來處理; 透過pthread_kill傳送的訊號(pthread_kill是pthread庫的介面, 對應的系統呼叫中tkill)被放線上程獨有的signal_pending中, 只能由本執行緒來處理.
當執行緒停止/繼續, 或者是收到一個致命訊號時, 核心會將處理動作施加到整個執行緒組中.
NGPT(Next Generation POSIX Threads)
上面提到的兩種執行緒庫使用的都是核心級執行緒(每個執行緒都對應核心中的一個排程實體), 這種模型稱為1:1模型(1個執行緒對應1個核心級執行緒);而NGPT則打算實現M:N模型(M個執行緒對應N個核心級執行緒), 也就是說若干個執行緒可能是在同一個執行實體上實現的.
執行緒庫需要在一個核心提供的執行實體上抽象出若干個執行實體, 並實現它們之間的排程. 這樣被抽象出來的執行實體稱為使用者級執行緒.大體上, 這可以透過為每個使用者級執行緒分配一個棧, 然後透過longjmp的方式進行上下文切換. (百度一下"setjmp/longjmp", 你就知道.)
但是實際上要處理的細節問題非常之多. 目前的NGPT好像並沒有實現所有預期的功能, 並且暫時也不準備去實現.
使用者級執行緒的切換顯然要比核心級執行緒的切換快一些, 前者可能只是一個簡單的長跳轉, 而後者則需要儲存/裝載暫存器, 進入然後退出核心態. (程式切換則還需要切換地址空間等).而使用者級執行緒則不能享受多處理器, 因為多個使用者級執行緒對應到一個核心級執行緒上, 一個核心級執行緒在同一時刻只能執行在一個處理器上.不過, M:N的執行緒模型畢竟提供了這樣一種手段, 可以讓不需要並行執行的執行緒執行在一個核心級執行緒對應的若干個使用者級執行緒上, 可以節省它們的切換開銷.
據說一些類UNIX系統(如Solaris)已經實現了比較成熟的M:N執行緒模型, 其效能比起linux的執行緒還是有著一定的優勢.
Linux檢視程式的所有子程式和執行緒
得到程式的pid:
ps -ef | grep process_name | grep -v "grep" | awk '{print $2}'
檢視程式的所有執行緒
# ps mp 6648 -o THREAD,tid
USER %CPU PRI SCNT WCHAN USER SYSTEM TID
root 0.0 - - - - - -
root 0.0 24 - - - - 6648
root 0.0 21 - - - - 6650
root 1.0 24 - - - - 14214
root 0.0 23 - futex_ - - 14216
root 0.0 22 - 184466 - - 15374
root 0.0 23 - 184466 - - 15376
root 0.0 23 - 184466 - - 15378
root 0.0 23 - 184466 - - 15380
root 0.0 23 - 184466 - - 15392
root 0.0 23 - 184466 - - 15394
root 0.0 23 - 184466 - - 15398
檢視所有子程式:
# pstree -p 6648
agent_executor(6648)─┬─tar(15601)───gzip(15607)
├─{agent_executor}(6650)
├─{agent_executor}(14214)
├─{agent_executor}(14216)
├─{agent_executor}(15374)
├─{agent_executor}(15376)
├─{agent_executor}(15378)
├─{agent_executor}(15380)
├─{agent_executor}(15392)
├─{agent_executor}(15394)
└─{agent_executor}(15398)
$i ${a}% ${b}% ${rate}% ${space_rate}% ${e} false
$c $g $d $imgcache_space_rate
檢視/proc/pid/status可以看到一些程式的當前狀態:
Name: bash
State: S (sleeping)
SleepAVG: 98%
Tgid: 11237
Pid: 11237
PPid: 11235
TracerPid: 0
Uid: 0 0 0 0
Gid: 0 0 0 0
FDSize: 256
Groups: 0 1 2 3 4 6 10
VmPeak: 66260 kB
VmSize: 66228 kB
VmLck: 0 kB
VmHWM: 1684 kB
VmRSS: 1684 kB
VmData: 456 kB
VmStk: 88 kB
VmExe: 712 kB
VmLib: 1508 kB
VmPTE: 68 kB
StaBrk: 008c3000 kB
Brk: 011b1000 kB
StaStk: 7fff8b728170 kB
Threads: 1
SigQ: 1/30222
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000010000
SigIgn: 0000000000384004
SigCgt: 000000004b813efb
CapInh: 0000000000000000
CapPrm: 00000000fffffeff
CapEff: 00000000fffffeff
Cpus_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000003
Mems_allowed: 1
LINUX中檢視程式的多執行緒
在SMP系統中,我們的應用程式經常使用多執行緒的技術,那麼在Linux中如何檢視某個程式的多個執行緒呢?
本文介紹3種命令來檢視Linux系統中的執行緒(LWP)的情況:
在我的系統中,用qemu-system-x86_64命令啟動了一個SMP的Guest,所以有幾個qemu的執行緒,以此為例來說明。
1. pstree 命令,檢視程式和執行緒的樹形結構關係。
|
[root@jay-linux ~]# pstree | grep qemu
|-gnome-terminal-+-bash---qemu-system-x86---2*[{qemu-system-x8}]
[root@jay-linux ~]# pstree -p | grep qemu
|-gnome-terminal(10194)-+-bash(10196)---qemu-system-x86(10657)-+-{qemu-system-x8}(10660)
| | `-{qemu-system-x8}(10661)
|
2. ps 命令,-L引數顯示程式,並儘量顯示其LWP(執行緒ID)和NLWP(執行緒的個數)。
|
[root@jay-linux ~]# ps -eLf | grep qemu
root 10657 10196 10657 0 3 13:48 pts/1 00:00:00 qemu-system-x86_64 -hda smep-temp.qcow -m 1024 -smp 2
root 10657 10196 10660 3 3 13:48 pts/1 00:00:26 qemu-system-x86_64 -hda smep-temp.qcow -m 1024 -smp 2
root 10657 10196 10661 2 3 13:48 pts/1 00:00:19 qemu-system-x86_64 -hda smep-temp.qcow -m 1024 -smp 2
root 10789 9799 10789 0 1 14:02 pts/0 00:00:00 grep --color=auto qemu
|
上面命令查詢結果的第二列為PID,第三列為PPID,第四列為LWP,第六列為NLWP。
另外,ps命令還可以檢視執行緒在哪個CPU上執行,命令如下:
|
[root@jay-linux ~]# ps -eo ruser,pid,ppid,lwp,psr,args -L | grep qemu
root 10657 10196 10657 1 qemu-system-x86_64 -hda smep-temp.qcow -m 1024 -smp 2
root 10657 10196 10660 1 qemu-system-x86_64 -hda smep-temp.qcow -m 1024 -smp 2
root 10657 10196 10661 2 qemu-system-x86_64 -hda smep-temp.qcow -m 1024 -smp 2
root 10834 9799 10834 1 grep --color=auto qemu
|
其中,每一列依次為:使用者ID,程式ID,父程式ID,執行緒ID,執行該執行緒的CPU的序號,命令列引數(包括命令本身)。
3. top 命令,其中H命令可以顯示各個執行緒的情況。(在top命令後,按H鍵;或者top -H)
|
[root@jay-linux ~]# top -H
top - 14:18:20 up 22:32, 4 users, load average: 2.00, 1.99, 1.90
Tasks: 286 total, 1 running, 285 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 3943892k total, 1541540k used, 2402352k free, 164404k buffers
Swap: 4194300k total, 0k used, 4194300k free, 787768k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
10660 root 20 0 1313m 188m 2752 S 2.3 4.9 0:46.78 qemu-system-x86
10661 root 20 0 1313m 188m 2752 S 2.0 4.9 0:39.44 qemu-system-x86
10867 root 20 0 15260 1312 960 R 0.3 0.0 0:00.07 top
1 root 20 0 19444 1560 1252 S 0.0 0.0 0:00.34 init
2 root 20 0 0 0 0 S 0.0 0.0 0:00.02 kthreadd
....
|
在top中也可以檢視程式(程式)在哪個CPU上執行的。
執行top後,按f,按j(選中* J: P = Last used cpu (SMP)),然後按空格或回車退出設定,在top的顯示中會多出P這一列是最近一次執行該執行緒(程式)的CPU.
|
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ P COMMAND
10661 root 20 0 1313m 188m 2752 S 2.3 4.9 0:44.24 3 qemu-system-x86
10660 root 20 0 1313m 188m 2752 S 2.0 4.9 0:51.74 0 qemu-system-x86
10874 root 20 0 15260 1284 860 R 0.7 0.0 0:00.32 2 top
1 root 20 0 19444 1560 1252 S 0.0 0.0 0:00.34 0 init
2 root 20 0 0 0 0 S 0.0 0.0 0:00.02 1 kthreadd
|
更多資訊,請 man pstree, man top, man ps 檢視幫助文件。
注: LWP為輕量級程式(即:執行緒),(light weight process, or thread) 。
0.最常用 pstree:
[root@iZ25dcp92ckZ temp]# pstree -a|grep multe
| | `-multepoolser
| | `-multepoolser
| | `-2*[{multepoolser}]
1. > top
可以顯示所有系統程式
按u, 再輸入相應的執行使用者名稱稱,比如Tom
可以看到Tom使用者啟動的所有程式和對應的pid
2. > pstack pid
可以看到此pid下,各執行緒的執行狀態、
[root@test multepoolserver]# pstack 14944 (程式的PID號)
Thread 2 (Thread 0x41ed5940 (LWP 14945)):
#0 0x0000003c9ae0d5cb in read () from /lib64/libpthread.so.0
#1 0x00000000004017b6 in sync_additional_writing_worker ()
#2 0x0000003c9ae064a7 in start_thread () from /lib64/libpthread.so.0
#3 0x0000003c9a2d3c2d in clone () from /lib64/libc.so.6
Thread 1 (Thread 0x2b24b3094250 (LWP 14944)):
#0 0x0000003c9a2d4018 in epoll_wait () from /lib64/libc.so.6
#1 0x0000000000401d59 in Process ()
#2 0x00000000004029b8 in main ()
來自:http://blog.csdn.net/wind_324/article/details/6152912
方法一:
ps -ef f
用樹形顯示程式和執行緒
在Linux下面好像因為沒有真正的執行緒,是用程式模擬的,有一個是輔助執行緒,所以真正程式開的執行緒應該只有一個。
方法二:
[root@apache dhj]# ps axm|grep httpd
方法三:
另外用pstree -c也可以達到相同的效果,但是沒有執行緒號:
[root@apache dhj]# pstree -c|grep httpd
來自:http://blog.chinaunix.net/uid-346158-id-2131012.html
1. pstree
pstree以樹結構顯示程式
-
root@119.10.6.*:~# pstree
-
init─┬─NetworkManager
-
├─abrt-dump-oops
-
├─abrtd
-
├─atd
-
├─auditd───{auditd}
-
├─automount───4*[{automount}]
-
├─certmonger
-
├─crond
-
├─dbus-daemon
-
├─hald─┬─hald-runner─┬─hald-addon-acpi
-
│ │ └─hald-addon-inpu
-
│ └─{hald}
-
├─httpd─┬─httpd
-
│ └─4*[httpd───26*[{httpd}]]
-
├─irqbalance
-
├─mcelog
-
├─6*[mingetty]
-
├─modem-manager
-
├─mysqld_safe───mysqld───38*[{mysqld}]
-
├─nginx───13*[nginx]
-
├─php-fpm───76*[php-fpm]
-
├─portreserve
-
├─rpc.idmapd
-
├─rpc.statd
-
├─rpcbind
-
├─rsyslogd───3*[{rsyslogd}]
-
├─2*[sendmail]
-
├─sshd───sshd───bash───pstree
-
├─udevd───2*[udevd]
-
├─wpa_supplicant
-
└─xinetd
2. ps -Lf
$ ps -Lf 1892
如下:PHP程式共啟動了0個執行緒
root@119.10.6.**:~# ps -Lf 1892
UID PID PPID LWP C NLWP STIME TTY STAT TIME CMD
root 1892 1 1892 0 1 Jan15 ? Ss 0:53 php-fpm: master process (/usr/local/php/etc/php-fpm.conf)
Mysql多個執行緒:
root@119.10.6.*:~# ps -Lf 3005
UID PID PPID LWP C NLWP STIME TTY STAT TIME CMD
mysql 3005 2011 3005 0 39 Jan15 ? Sl 2:25 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
mysql 3005 2011 3081 0 39 Jan15 ? Sl 0:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
mysql 3005 2011 3082 0 39 Jan15 ? Sl 0:05 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
mysql 3005 2011 3083 0 39 Jan15 ? Sl 0:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
mysql 3005 2011 3084 0 39 Jan15 ? Sl 0:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
mysql 3005 2011 3085 0 39 Jan15 ? Sl 0:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
mysql 3005 2011 3086 0 39 Jan15 ? Sl 0:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
mysql 3005 2011 3087 0 39 Jan15 ? Sl 0:05 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=
3. pstack
pstack顯示每個程式的棧跟蹤,PHP的fpm主程式,發現這玩意是走的epoll:
root@119.10.6.23:~# pstack 1892
#0 0x00000030e9ae8fb3 in __epoll_wait_nocancel () from /lib64/libc.so.6
#1 0x0000000000856a74 in fpm_event_epoll_wait ()
#2 0x000000000084afff in fpm_event_loop ()
#3 0x0000000000845ee7 in fpm_run ()
#4 0x000000000084d900 in main ()
You have new mail in /var/spool/mail/root
Question: My program creates and executes multiple threads in it. How can I monitor individual threads of the program once they are created? I would like to see the details (e.g., CPU/memory usage) of individual threads with their names.
Threads are a popular programming abstraction for parallel execution on modern operating systems. When threads are forked inside a program for multiple flows of execution, these threads share certain resources (e.g., memory address space, open files) among themselves to minimize forking overhead and avoid expensive IPC (inter-process communication) channel. These properties make threads an efficient mechanism for concurrent execution.
In Linux, threads (also called Lightweight Processes (LWP)) created within a program will have the same "thread group ID" as the program's PID. Each thread will then have its own thread ID (TID). To the Linux kernel's scheduler, threads are nothing more than standard processes which happen to share certain resources. Classic command-line tools such as psor top, which display process-level information by default, can be instructed to display thread-level information.
Here are several ways to show threads for a process on Linux.
Method One: PS
In ps command, "-T" option enables thread views. The following command list all threads created by a process with .
$ ps -T -p
The "SID" column represents thread IDs, and "CMD" column shows thread names.
Method Two: Top
The top command can show a real-time view of individual threads. To enable thread views in the top output, invoke topwith "-H" option. This will list all Linux threads. You can also toggle on or off thread view mode while top is running, by pressing 'H' key.
$ top -H
To restrict the top output to a particular process and check all threads running inside the process:
$ top -H -p
Method Three: Htop
A more user-friendly way to view threads per process is via , an ncurses-based interactive process viewer. This program allows you to monitor individual threads in tree views.
To enable thread views in htop, launch htop, and press to enter htop setup menu. Choose "Display option" under "Setup" column, and toggle on "Three view" and "Show custom thread names" options. Presss to exit the setup.
Now you will see the follow threaded view of individual processes.
Download this article as ad-free PDF (made possible by ):
About Me
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26736162/viewspace-2144188/,如需轉載,請註明出處,否則將追究法律責任。