轉載出處:https://blog.csdn.net/skyroben/article/details/72793409
一、背景知識
Linux沒有真正意義上的線程,它的實現是由進程來模擬,所以屬於用戶級線程,位於libpthread共享庫(所以線程的ID只在庫中有效),遵循POSIX標准。
Windows下有一個真正的數據結構TCB來描述線程。
Linux上兩個最有名的線程庫LinuxThreads和NPTL。
Linux兩個線程模型的比較:
Linux下多線程虛擬地址空間的映射類似於用vfork創建多個子進程。
二、進程和線程的區別
進程:程序的一個動態運行實例,承擔分配系統資源的實例。(Linux實現進程的主要目的是資源獨占)
線程:在進程的內部運行(進程的地址空間)運行的一個分支,也是調度的基本單位(調度按LWP調度)。(Linux實現線程的主要目的是資源共享)
線程所有的資源由進程提供。
單進程:只有一個進程的線程(LWP=PID)。
LWP:輕量級進程。
由於同一進程的多個線程共享同一地址空間,因 此Text Segment、Data Segment都是共享的,如果定義一個函數,在各線程中都可以調用,如果定義一個全局變量,在各線程中都可以訪問到,除此之外,各線程還共享以下進程資源和環境:
1. 文件描述符表2. 每種信號的處理方式(SIG_IGN、SIG_DFL或者自定義的信號處理函數)3. 當前工作目錄4. 用戶id和組id
但有些資源是每個線程各有一份的:
1.線程ID2. 上下文信息,包括各種寄存器的值、程序計數器和棧指針3. 棧空間4. errno變量5. 信號屏蔽字6. 調度優先級
多線程程序的優點(相對進程比較而言):
1. 多個線程,它們彼此之間使用相同的地址空間,共享大部分數據,啟動一個線程所花費的空間遠遠小於啟動一個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小於進程間切換所需要的時間,創建銷毀速度快。
2.是線程間方便的通信機制。由於同一進程下的線程之間共享數據空間,所以一個線程的數據可以直接為其它線程所用,這不僅快捷,而且方便。
三、進程控制
在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);
(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,失敗返回錯誤碼。可能出現的錯誤碼:

四、分離線程
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; } }
運行結果:

分析:
可以看到被分離的線程不可以被等待;一個線程初始為可結合的,要么在父線程等待或分離,要么在子線程分離,只能采取上述幾種措施的一種。