1. 程式人生 > >Linux — POSIX 執行緒基礎

Linux — POSIX 執行緒基礎

執行緒對於Linux後臺程式設計師來說並不陌生,執行緒帶給我們併發能力的提升,也提高了軟體開發和問題定位的難度,本文

嘗試結合GlibC 程式碼, 對POSIX的執行緒做一個簡單說明,重點介紹執行緒的建立,釋放和連線上需要注意的問題。

多程序和多執行緒的都只有一個目的,並行處理,提高CPU利用率。相比程序,執行緒的優勢體現在以下幾個方面:

1.執行緒建立銷燬開銷小於程序

2.執行緒上下文切換開銷小於程序

3.執行緒間通訊優於程序

針對第三點,這針對第三點,這是一個見仁見智的問題,一個程序下的所有執行緒共享記憶體地址,所以執行緒間的通訊可

以很隨意,但是為了維護對公共資源的有序讀寫,又引入了鎖,訊號量等機制,這些工具一旦使用不當會出現死鎖,

臨界區競爭等問題,所以多執行緒的模式也提高了程式設計難度和定位問題的複雜度.

Linux的執行緒也叫 輕量級執行緒(LWP, light weight process),來自 Native POSIX Thread Library (NPTL)庫的實現

,該庫在1995年被POSIX.1c定為標準API。每個執行緒擁有自己的task_struct,所以用獨立的堆疊空間,能獨立的夠被

CPU排程。

 #include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

引數性質:

分別為,執行緒ID,執行緒屬性,執行函式入口,執行緒執行函式引數.

POSIX 執行緒的第一個引數 thread 型別為 pthread_t,本質是long int 型。用於返回程序中唯一標識ID。因為該ID只在

程序中唯一識別執行緒,所以可稱為區域性執行緒ID(相對後面用於CPU排程,pid_t型別的全域性執行緒ID而言)。在NPTL

中,thread 儲存的是執行緒描述結構(struct pthread)的地址。

由於棧地址是複用的,所以thread_t* thread的值也會重複,如果主執行緒建立了一個執行緒後退出,那麼再建立的執行緒

其 thread 值會和前面的執行緒一樣的

POSIX 執行緒是CPU的排程實體,所以CPU需要一個唯一ID標示不同的執行緒,這就是pid_t型別的全域性執行緒ID。Linux

的程序由一個或多個執行緒組成,所以程序也叫執行緒組,執行緒組內的第一個執行緒稱為主執行緒,該執行緒ID就是執行緒組

ID,也是程序ID。而之後建立的執行緒,其執行緒組ID(程序ID)不變,每個執行緒有獨立的,pid_t型別的LWP ID,CPU

就是根據該ID獲得執行緒上下文資訊(struct_task)實現執行緒排程.

 

執行緒的棧:

程序的記憶體分佈佈局中,使用者空間分為我們熟知的程式碼段,已初始化資料段,未初始化資料段,堆,棧等等.  隨著

程序啟動,使用者記憶體空間單獨劃分一段區域為主執行緒棧,主執行緒棧的大小可以動態變化,從高地址向下擴充套件,隨後

建立的執行緒棧大小則是固定的,執行緒棧通過mmap方式在記憶體對映區劃分記憶體,執行緒棧大小可以通過ulimit -s

檢視,預設為8192kb大小,執行緒棧之間有一個保護區,該區域被訪問觸發會發訊號告警,保護區預設大小是一個

記憶體頁(4096位元組).

 

執行緒退出:

 

#include <pthread.h>

 void pthread_exit(void *retval);

其中引數retval是要退出後要上報的結果,其他執行緒可以通過pthread_join得到該值. 

需要注意的是,retval要求指向的地址不會因為執行緒退出而失效,這說明用執行緒變數儲存返回值是不靠譜的.

 

執行緒的連線:

 

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

pthread_join函式用於取出其他執行緒退出後上報的結果,只能針對可連線的執行緒(joinable)執行緒使用,而且每個執行緒

只能被;連線一次。 引數thread是上下文提到的區域性執行緒ID,執行緒不能自己連線自己,既不能在thread填寫自己的

區域性執行緒ID.  

pthread_join 的作用和程序中的waitpid非常像,waitpid是父程序為子程序“收屍”,pthread_join也是一個執行緒為另

一個執行緒“收屍”。父程序如果不進行wait操作,子程序退出後就會成為殭屍程序,執行緒也一樣,可連線(joinable

)的執行緒在退出後如果沒有其他執行緒呼叫pthread_join 接住它的退出狀態,那它也同樣不會釋放執行緒的資源。調

用pthread_join的執行緒在接收到返回狀態前會陷入阻塞。

和程序的wait不同,父程序可以等待任何一個子程序退出,但是執行緒必須明確指定要等待的執行緒id。這是因為執行緒

之間沒有父執行緒和子執行緒層級關係。一個程序也就是一個執行緒組,裡面只有一個主執行緒,其他的都是衍生出來的

執行緒,人人平等,即便主執行緒呼叫pthread_exit退出了(不建議這麼做),程序還是能繼續執行。

 

執行緒分離:

 

如果執行緒覺得退出也沒什麼遺言,可以把執行緒屬性設定為分離狀態,執行緒的分離狀態可以在建立執行緒時配置屬性

來調整,pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED)也可以在任何執行緒中呼叫

pthread_detach函式來調整該屬性

 

#include <pthread.h>

int pthread_deatch(pthread_t thread);

較為普遍的做法是執行緒內部自己修改分離狀態。

pthread_detach(pthread_self())

執行緒處於分離狀態表示退出後沒有任何遺言,隨著執行緒結束馬上交出棧資源,所以也無需其他執行緒連線它。

pthread_join 分離狀態的執行緒,會返回錯誤 EINVAL。