轉載出處:https://blog.csdn.net/skyroben/article/details/72793409
1.背景知識
Linux沒有真正意義上的線程,它的實作是由程序來模拟,是以屬于使用者級線程,位于libpthread共享庫(是以線程的ID隻在庫中有效),遵循POSIX标準。
Windows下有一個真正的資料結構TCB來描述線程。
Linux上兩個最有名的線程庫LinuxThreads和NPTL。
Linux兩個線程模型的比較:
Linux線程模型的比較
Linux下多線程虛拟位址空間的映射類似于用vfork建立多個子程序。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0NXYFhGd192UvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2Lc1DNXpVa502Y1QnMjZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DO2ITOxMDM2EDOyUDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
2.程序和線程的差別
程序:程式的一個動态運作執行個體,承擔配置設定系統資源的執行個體。(Linux實作程序的主要目的是資源獨占)
線程:在程序的内部運作(程序的位址空間)運作的一個分支,也是排程的基本機關(排程按LWP排程)。(Linux實作線程的主要目的是資源共享)
線程所有的資源由程序提供。
單程序:隻有一個程序的線程(LWP=PID)。
LWP:輕量級程序。
由于同一程序的多個線程共享同一位址空間,因 此Text Segment、Data Segment都是共享的,如果定義一個函數,在各線程中都可以調用,如果定義一個全局變量,在各線程中都可以通路到,除此之外,各線程還共享以下程序資源和環境:
1. 檔案描述符表 2. 每種信号的處理方式(SIG_IGN、SIG_DFL或者自定義的信号處理函數)
3. 目前工作目錄 4. 使用者id群組id
但有些資源是每個線程各有一份的:
1.線程ID
2. 上下文資訊,包括各種寄存器的值、程式計數器和棧指針
3. 棧空間
4. errno變量
5. 信号屏蔽字
6. 排程優先級
多線程程式的優點(相對程序比較而言):
1. 多個線程,它們彼此之間使用相同的位址空間,共享大部分資料,啟動一個線程所花費的空間遠遠小于啟動一個程序所花費的空間,而且,線程間彼此切換所需的時間也遠遠小于程序間切換所需要的時間,建立銷毀速度快。
2.是線程間友善的通信機制。由于同一程序下的線程之間共享資料空間,是以一個線程的資料可以直接為其它線程所用,這不僅快捷,而且友善。
3.程序控制
在Linux系統下,與線程相關的函數都定義在pthread.h頭檔案中。
建立線程函數———pthread_create函數
- #include <pthread.h>
- int pthread_create(pthread_t * thread, const pthread_arrt_t* attr,void*(*start_routine)(void *), void* arg);
(1)thread參數是新線程的辨別符,為一個整型。
(2)attr參數用于設定新線程的屬性。給傳遞NULL表示設定為預設線程屬性。
(3)start_routine和arg參數分别指定新線程将運作的函數和參數。start_routine傳回時,這個線程就退出了
(4)傳回值:成功傳回0,失敗傳回錯誤号。
線程id的類型是thread_t,它隻在目前程序中保證是唯一的,在不同的系統中thread_t這個類型有不同的實作,調用pthread_self()可以獲得目前線程的id
程序id的類型時pid_t,每個程序的id在整個系統中是唯一的,調用getpid()可以獲得目前程序的id,是一個正整數值。
終止線程———pthread_cancel函數和pthread_exit函數
終止某個線程而不終止整個程序,可以有三種方法:
1. 從線程函數return。這種方法對主線程不适應,從main函數return相當于調用exit。
2. 一個線程可以調用pthread_cancel終止同一程序中的另一個線程。
3. 線程可以調用pthread_exit終止自己。
- #include <pthread.h>
- int pthread_cancel(pthread_t thread);
(1)thread參數是目标線程的辨別符。
(2)該函數成功傳回0,失敗傳回錯誤碼。
- #include <pthread.h>
- void pthread_exit(void * retval);
(1)retval是void *類型,其它線程可以調用pthread_join獲得這個指針。需要注意,pthread_exit或者return傳回的指針所指向的記憶體單元必須是全局的或者是由malloc分 配的,不能線上程函數的棧上配置設定,因為當其它線程得到這個傳回指針時線程函數已經退出了。
(2)pthread_exit函數通過retval參數向線程的回收者傳遞其退出資訊。它執行之後不會傳回到調用者,且永遠不會失敗。
線程等待———pthread_join
- #include <pthread.h>
- void pthread_join(pthread_t thread,void ** retval);
(1)調用該函數的線程将挂起等待,直到id為thread的線程終止。
(2)thread線程以不同的方法終止,通過pthread_join得到的終止狀态是不同的,
總結如下:
1. 如果thread線程通過return傳回,value_ptr所指向的單元裡存放的是thread線程函數的傳回值。
2. 如果thread線程被别的線程調用pthread_cancel異常終掉,value_ptr所指向的單元裡存放的是常數PTHREAD_CANCELED。
3. 如果thread線程是自己調用pthread_exit終止的,value_ptr所指向的單元存放的是傳給pthread_exit的參數。 如果對thread線程的終止狀态不感興趣,可以傳NULL給value_ptr參數。
(3)成功傳回0,失敗傳回錯誤碼。可能出現的錯誤碼:
4.分離線程
1.在任何一個時間點上,線程是可結合的(joinable)或者是分離的(detached)。
2.一個可結合的線程能夠被其他線程收回其資源和殺死。在被其他線程回收之前,它的存儲器資源
(例如棧)是不釋放的。(預設情況下線程的建立都是可結合的)
3.一個分離的線程是不能被其他線程回收或殺死的,它的存儲器 資源在它終止時由系統自動釋放。
4. 如果一個可結合線程結束運作但沒有被join,會導緻部分資源沒有被回收,是以建立線程者應該調用pthread_join來等待線程運作結束,并可得到線程的退出代碼,回收其資源。 調用pthread_join後,如果該線程沒有運作結束,調用者會被阻塞。如何解決這種情況呢? 例如,在Web伺服器中當主線程為每個新來的連接配接請求建立一個子線程進行處理的時候,主線程并不希望因為調用pthread_join而阻塞(因為還要繼續處理之後到來的連接配接請求),這時可以在子線程中加入代碼 pthread_detach(pthread_self())或者父線程調用pthread_detach(thread_id)(非阻塞,可立即傳回)這将該子線程的狀态設定為分離的(detached),如此一來,該線程運作結束後會自動釋放所有資源。
驗證代碼:
- #include <stdio.h>
- #include <error.h>
- #include <stdlib.h>
- #include <pthread.h>
- void* thread_run(void* _val)
- {
- pthread_detach(pthread_self()); //注釋這句代碼join success
- printf( "%s\n", ( char*)_val);
- return NULL;
- }
- int main()
- {
- pthread_t tid;
- int tret = pthread_create(&tid, NULL, thread_run, "thread_run~~~~~");
- //線程建立成功之後,程式的執行流變成兩個,一個執行函數thread_run,一個繼續向下執行。
- if (tret == 0)
- {
- sleep( 1);
- int ret = pthread_join(tid, NULL);
- if (ret == 0)
- {
- printf( "pthread_join success\n");
- return ret;
- }
- else
- {
- printf( "pthread_join failed info: %s\n", strerror(ret));
- return ret;
- }
- }
- else
- {
- printf( "create pthread failed info: %s", strerror(tret));
- return tret;
- }
- }
運作結果:
分析: 可以看到被分離的線程不可以被等待;一個線程初始為可結合的,要麼在父線程等待或分離,要麼在子線程分離,隻能采取上述幾種措施的一種。