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。