1. 程式人生 > 其它 >【Linux】執行緒的那些基礎知識,(thread)

【Linux】執行緒的那些基礎知識,(thread)

------------恢復內容開始------------

一. 什麼是執行緒?

1. 執行緒概念

執行緒(thread),是程序中的一條執行流,是被系統獨立排程和分派的基本單位。一個標準的執行緒由執行緒ID、當前指令指標、暫存器集合和堆疊組成,此外一個執行緒可與同屬一個程序組的其他執行緒共享程序所擁有的全部資源,同一程序中的多個執行緒之間可以併發執行。

執行緒是程式中一個單一順序執行流,在單個程式中同時執行多個執行緒完成不同的工作,稱為多執行緒。

2. 重新理解程序

一開始學習程序時,老師告訴我們程序的概念是:

  • 一個執行起來的程式叫做程序。
  • 每個程序系統都會為其分配一個:task_struct(PCB)、mm_struct(程序地址空間)和頁表這三個資料結構來描述和管理程序。

下圖是站在使用者的角度(俯視)去理解程序的。

在一個程序裡的一個執行流就叫做執行緒,每一個程序至少都有一個主執行流,即main函式。這一個個執行流的特徵包括:

  • 每個執行流擁有自己專屬的task_struct。
  • 所有執行流共用同一個程序地址空間和頁表。
  • 透過程序虛擬地址空間,可以看到程序的大部分資源,作業系統將程序資源合理分配給每個執行流,就形成了執行緒執行流。

站線上程的角度上,我們之前理解的程序是:具有一個執行緒執行流(執行緒)的程序。

站在系統的角度(仰視)上:程序是承擔系統資源分配的基本實體。通常在一個程序中可以包含若干個執行緒,這些執行緒可以利用程序所擁有的資源。在引入執行緒的作業系統中,通常是把程序作為分配系統資源的基本單位,而把執行緒作為獨立執行和獨立排程的基本單位。由於執行緒比程序小,故它的排程所付出的開銷就會小得多,能更高效提高系統內多個程式間併發執行的效率,從而顯著提高系統資源的利用率和吞吐率。

PS:執行緒也稱為輕量級程序。像程序一樣,執行緒在程式中有獨立的、併發的執行路徑,每個執行緒都有它自己私有的棧空間、自己的程式計數器和自己的區域性變數。但是他們共享全域性資料區、檔案描述符和其他每個程序應有的狀態。

3. 執行緒優缺點

優點:

  • 建立一個新執行緒的代價要比建立一個新程序小得多。
  • 切換兩個執行緒的時間比切換兩個程序的時間少的多。
  • 執行緒間通訊比程序間通訊容易,因為執行緒間資源共享。

缺點:

  • 健壯性降低。編寫多執行緒需要更全面更深入的考慮,在一個多執行緒程式裡,因時間分配上的細微偏差或者因共享了不該共享的變數而造成不良影響的可能性是很大的,換句話說執行緒之間是缺乏保護的。
  • 程式設計難度提高。編寫與除錯一個多執行緒程式比單執行緒程式困難得多,而且一個程序出現異常會導致整個程序都掛掉。

4. 執行緒週期

執行緒生命週期由新建、就緒、執行、阻塞、死亡五部分組成。

5. 執行緒排程

當有執行緒進入了就緒狀態,需要由執行緒排程程式來決定何時執行,根據優先順序來排程。

6. 執行緒工作原理

一個程序中的多個執行緒共享相同的記憶體地址空間,除了棧以外共用其他所有的資料空間。這就意味著它們可以訪問相同的變數和物件。儘管這讓執行緒之間共享資訊變得更容易,但必須小心,確保它們不會妨礙同一程序裡的其他執行緒。

7. 執行緒異常

多執行緒沒有記憶體隔離,其中一個執行緒發生異常(比如除零,野指標等)導致執行緒異常崩潰,作業系統接收到異常訊號後為了絕對安全的考慮會把整個程序的資料結構全銷燬,包括:

  • 所有執行緒的task_struct。
  • 共用的mm_struct。
  • 共用的頁表。

一個執行緒異常導致整個程序崩潰,所以多執行緒程式除錯起來較為麻煩。

8. 執行緒資源

main函式和訊號處理函式是同一個程序地址空間中的多個控制流程,多執行緒也是如此,但是比訊號處理函式更靈活。訊號處理函式的控制流程只是在訊號遞達時產生,在處理完訊號之後就結束,而多執行緒的控制流程可以長期存在,作業系統會在個執行緒之間排程和切換,就像在多個程序之間排程和切換一樣。由於同一程序的多個執行緒共享同一地址空間,因此資料段、程式碼段都是共享的。如果定義一個函式,在各執行緒中都可以呼叫,如果定義一個全域性變數,在各執行緒中都可以訪問到。下面總結一下所有執行緒共享的資源:

  • 程序地址空間。
  • 開啟檔案描述符表。
  • 訊號的處理方式(SIG_IGN、SIG_DFL或者自定義訊號處理函式)。
  • 當前工作目錄。
  • 使用者ID和組ID。

下面是每個執行緒獨有的資源:

  • 執行緒ID。
  • 棧空間。
  • errno變數。
  • 排程優先順序。
  • 訊號遮蔽字(blocked表)。
  • 上下文、程式計數器和一組暫存器。

二. 為什麼要有執行緒?

執行緒在地址空間中執行。以前伺服器端提供服務多采用多程序機制,而多程序機制需要fork子程序,子程序fork後需要單獨的地址空間和其他系統資源,消耗系統開銷和資源較大。並且程序之間共享資料需要用程序間通訊機制,這也增加了程式設計難度。

現在較多的服務端程式採用多執行緒機制提供服務,這種機制消耗資源少,也便於執行緒間共享資料,執行緒也有單獨的堆疊空間,但消耗的時間、空間成本比程序少許多。在一個程序的地址空間中執行多個執行緒,但其共享程序系統資源和全域性資料。

三. 如何控制執行緒?

1. Linux支援的POSIX執行緒庫

很早之前,還沒有Linux Kernel的時候,是Unix的天下。Unix是一款開源的系統,很多開發者都基於Unix做各種定製開發並開源出來,一時間各種類Unix系統層出不窮,局面一度非常混亂。為了提升各版本系統的相容性和支援跨平臺開發,IEEE釋出了POSIX標準。POSIX全稱是Portable Operating System Interface for Computing Systems,它定義了具備可移植作業系統的各種標準,其中關於執行緒的標準參考:pthreads。目前包括Linux、Windows、macOS、iOS等系統都是相容或部分相容POSIX標準的。

Linux中與執行緒有關的函式被打包到動態庫/lib64/libpthread.so裡,由於是POSIX提供的庫,所以絕大多數函式的名字都是以“pthread_”打頭的。

  • 要使用這些庫函式,要通過引入頭文<pthread.h>。
  • 連結這些執行緒函式庫時要使用編譯器命令的“-lpthread”選項。

2. 執行緒建立

POSIX通過pthread_create()函式建立程序,該函式的原型如下:

返回值:成功返回0,失敗返回錯誤碼。系統函式一般都是成功返回0,失敗返回-1,並把錯誤碼儲存在全域性變數errno中。而pthread庫的函式都是通過返回值直接返回錯誤碼,雖然每個執行緒都有自己的errno,但這是為了相容其他函式介面而提供的,pthread庫本身並不使用它,POSIX認為通過返回值返回錯誤碼更加清晰並且讀取返回值的開銷要比讀取執行緒內的errno變數的開銷要小。

引數說明:
  ① 當函式成功時,執行緒識別符號儲存在輸出型引數thread指向的記憶體中,該引數的型別為pthread_t,代表執行緒ID。

  ② 引數attr中含有初始化執行緒所需要的屬性。如果不指定物件的屬性,將其置為NULL,表示建立一個預設的執行緒,其屬性為非繫結的、未分離的、有一個預設大小的堆疊,具有和父程序一樣的優先順序。

  ③ start_routine是執行緒入口函式的地址,該函式有一個void型別的引數並且返回一個void型別的值。當start_routine函式返回時,相應的執行緒就結束了。

  ④ arg表示要傳遞給start_routine函式的引數,型別為void*。

函式說明:
在一個執行緒中呼叫pthread_create()建立新的執行緒後,當前執行緒從pthread_create()返回繼續往下執行,而新的執行緒執行程式碼由strat_routine函式指標決定。strat_routine函式指標接受一個引數,是通過pthread_create函式的arg引數傳遞給它的,該引數的型別為void*,這個指標按什麼型別解釋由呼叫者自己定義。start_routine返回值型別也是void*,這個指標的型別同樣由呼叫者自己定義。start_routine返回時,這個執行緒就退出了,其他執行緒可以呼叫pthread_join得到start_routine的返回值,這類似於父程序呼叫wait()得到子程序的退出狀態。

函式使用舉例:
在主執行緒(main執行流)中使用pthread_create()建立一個新執行緒,之後主執行緒和新執行緒都使用while迴圈每隔一秒列印一句話。

#include <stdio.h>    
#include <unistd.h>    
#include <pthread.h>    
    
void* Routine(void* arg)    
{
       
  while(1)    
  {
       
    printf("--------------- I am %s\n", (const char*)arg);    
    sleep(1);    
  }    
  return NULL;
}    
    
int main()    
{
       
  pthread_t tid;    
  pthread_create(&tid, NULL, Routine, (void*)"thread 1");    
  while(1)    
  {
       
    printf("I am main thread\n");                                                                                     
    sleep(1);    
  }    
  return 0;    
} 

編譯執行:

ps -aL命令

使用ps -aL命令可以檢視當前會話中所有執行緒的屬性資訊:

標誌 含義
PID 程序ID
LWP 輕量級程序ID(注意不是執行緒ID)
TTY 登入者的終端機位置,若為遠端登入則使用動態終端介面 (pts/n)
TIME 使用掉的 CPU 時間,注意,是實際花費掉的 CPU 運作的時間,而不是系統時間
CMD 就是 command 的縮寫,產生此執行緒的指令

執行緒ID、LWP、執行緒組ID

1、執行緒ID就是pthread_create()函式傳入的第一個引數,它的型別為pthread_t,實際上是一個無符號長整型的重定義:

typedef unsigned long int pthread_t;

執行緒ID是POSIX執行緒庫設定的在使用者角度唯一標識執行緒的值。pthread庫把執行緒ID提供給使用者,使用者拿到執行緒ID後可以使用pthread庫裡的執行緒控制函式介面對特定執行緒進行刪除、等待、分離等操作。

2、LWP全拼light weight process即輕量級程序ID,它的本質是該執行緒task_struct結構體裡的pid變數,LWP是站在核心的角度唯一標識執行緒的。

3、每個執行緒都是一個執行緒組裡的一個成員,執行緒組把多個執行緒集合成在一起,通過執行緒組,可以同時對其中的多個執行緒進行操作。在生成執行緒時,必須將其放在指定的執行緒組中,也可以放在預設的執行緒組中,預設的就是生成該執行緒所在的執行緒組。一旦一個執行緒加入了某個執行緒組,就不能被移出這個組。

  • 主執行緒會預設會自己創立一個執行緒組,執行緒組ID等於主執行緒的LWP。
  • 其他在這個主執行緒之下直接或間接建立的新執行緒預設和主執行緒同屬一個執行緒組。
  • 預設情況:程序ID = 主執行緒的LWP = 執行緒組ID

在每一個執行緒的task_struct裡都存有它所線上程組的執行緒組ID,叫做tgid,全稱thread group ID:

4、三者關係總結

pthread_self()和執行緒ID的含義

pthread_create函式成功返回後,新建立的執行緒ID被填寫到第一個引數所指向的記憶體單元中。程序ID的型別是pid_t,每個程序ID在整個系統中是唯一的,呼叫getpid()可以獲得當前程序ID,是一個正整數值。執行緒ID的型別是pthread_t,它只在當前程序中保證是唯一的,在不同系統中pthread_t這個型別有不同的實現,它可能是一個整數值、結構體,甚至可能是一個地址,所以不能簡單地當成整數而使用printf列印,呼叫pthread_self()可以獲得當前執行緒ID。

函式原型:pthread_t pthread_self(void);
返回值:返回呼叫執行緒自己的執行緒ID

在Linux中執行緒ID的含義是指向該執行緒私有資源的首元素地址:

我們讓主執行緒和新執行緒分別呼叫pthread_self()函式拿到它自己的執行緒ID,並用printf以地址%p的格式分別列印它們自己的執行緒ID:

#include <stdio.h>
#include <unistd.h>    
#include <pthread.h>    
      
void* Routine(void* arg)    
{
       
  while(1)    
  {
       
    printf("----------------------- I am thread 1,tid is:%p\n", (void*)pthread_self());
    sleep(1);
  }
  return NULL;
}

int main()
{
   
  pthread_t tid;
  pthread_create(&tid, NULL, Routine, NULL);
  while(1)
  {
   
    printf("I am main thread,tid is:%p\n", (void*)pthread_self());
    sleep(1);
  }
  return 0;
}

編譯執行:

3. 執行緒等待

POSIX通過pthread_join()函式來等待程序退出,該函式的原型如下:

引數說明:

  • thread:想要等待的執行緒ID。
  • retval:如果該引數不為NULL,則將執行緒退出碼放在retval指向的記憶體中。實際使用時我們可以建立一個void*型別的變數,然後把該變數取地址傳入。

返回值:等待成功返回0,失敗返回錯誤碼。執行緒ID為thread的執行緒以不同的方法終止,通過pthread_join得到的終止狀態是不同的,總結如下:

  • 如果thread執行緒通過return返回,retval所指向的單元裡存放的是退出執行緒return的返回值。
  • 如果thread執行緒被別的執行緒呼叫pthread_ cancel()異常終止,retval所指向的單元裡存放的是常數PTHREAD_ CANCELED,其值為巨集定義,可以在標頭檔案pthread.h中找到它的定義:#define PTHREAD_ CANCELED ((void*)-1)。
  • 如果thread執行緒是自己呼叫pthread_exit()自我退出的,retval所指向的單元存放的是傳給pthread_exit()的引數。
  • 如果對thread執行緒的終止狀態不感興趣,可以傳NULL給retval引數。

函式說明:
① 呼叫該函式的執行緒將掛起等待,直到執行緒ID為thread的執行緒終止。

② thread指定的執行緒必須在當前程序中,同時,thread指定的執行緒必須是非分離的。

③ 不能有多個執行緒等待同一個執行緒終止。如果出現這種情況,一個執行緒將成功返回,別的執行緒將返回錯誤碼ESRCH。

函式使用舉例
我們建立三個新執行緒,在主執行緒中使用pthread_join()等待這三個新執行緒退出並列印它們的退出碼:

#include <stdio.h>                                                                                                  
#include <pthread.h>
#include <unistd.h>

void* Routine(void* arg)
{
   
  for(int i = 0; i < 3; ++i)
  {
   
    printf("I am %s,runing\n", (const char*)arg);
    sleep(1);
  }
  return (void*)123;
}

int main()    
{
       
  pthread_t tid[3];    
  const char* str[3] = {
   "thread 1", "thread 2", "thread 3"};    
  // 1、迴圈建立三個執行緒    
  for(int i = 0; i < 3; ++i)    
  {
       
    pthread_create(&tid[i], NULL, Routine, (void*)str[i]);    
  }    
  // 2、等待三個子執行緒退出    
  for(int i = 0; i < 3; ++i)    
  {
       
    void* status = NULL;    
    pthread_join(tid[i], &status);    
    printf("%s quit,exit code is %d\n", str[i], (int)status);    
    sleep(1);    
  }                                                                                                                 
  return 0;    
}  

編譯執行:

問題1:為什麼需要執行緒等待?

  • 已經退出的執行緒,其資源沒有被釋放,需要其它執行緒等待它退出然後清理它的資源。
  • 一個執行緒需要知道另一個執行緒把任務完成的怎麼樣了,這時需要通過等待來獲得退出執行緒的退出碼。

問題2:如何得到被異常終止的執行緒的退出狀態?

一個執行流終止有三種情況:

  • 正常終止,結果正確。
  • 正常終止,結果錯誤。
  • 異常終止,導致整個程式崩潰。

前兩種正常終止的情況可以通過最終的返回值來判斷結果到底是正確還是錯誤。而異常終止的話可以通過收到的終止訊號來分析異常出現的原因。

在程序中,父程序可以通過waitpid函式傳入輸出型引數得到子程序的退出狀態,甚至如果子程序異常退出,父程序也可以得到導致子程序退出的訊號。那麼一個執行緒異常退出,同組的其他執行緒能不能拿到導致執行緒異常退出的訊號呢?答案是不能的,因為一個執行緒異常會導致整個執行緒組的所有執行緒都退出,即同組的執行緒想要分析導致那個執行緒異常終止的訊號時,自己也被作業系統清理了。

4. 執行緒終止

如果只需要終止某個執行緒終止而不是終止整個程序,可以有以下三種方法:

  ① 從執行緒函式return返回,這種方法對主執行緒不適用,因為從main函式return返回相當於呼叫exit退出。

  ② 一個執行緒可以呼叫pthread_cancel函式終止同組的其他執行緒。

  ③ 執行緒呼叫pthread_exit函式會自我終止。

執行緒終止方式 thread_join函式得到的終止狀態
非主執行緒呼叫return return的返回值
phread_cancel(tid) 常數PTHREAD_ CANCELED,即(void*)-1
pthread_exit((void*)返回值) 傳給pthread_exit的引數

注意事項:

  • 在有多個執行緒的情況下,主執行緒從main函式的return返回,則整個程序退出。如果其他執行緒使用pthread_cancel()終止主執行緒或主執行緒自己呼叫pthread_cancel()函式自我終止, 那麼主執行緒的狀態變更成為Z, 其他執行緒不受影響。
  • pthread_exit或者return返回的指標所指向的記憶體單元必須是全域性的或者是用malloc分配的,不能線上程函式的棧上分配,因為當其它執行緒得到這個返回指標時執行緒函式已經退出了。

問題:執行緒可不可以用exit只終止自己?

  答:不可以,exit不論是使用在主執行緒還是子執行緒作用都是終止掉整個程序。

下面我們建立一個子執行緒,然後在子執行緒中呼叫exit函式:

#include <stdio.h>    
#include <pthread.h>    
#include <unistd.h>    
#include <stdlib.h>    
    
void* Routine(void* arg)    
{
       
    printf("I am %s\n", (const char*)arg);    
    exit(0);    
}    
    
int main()    
{
       
  pthread_t tid;    
  pthread_create(&tid, NULL, Routine, (void*)"thread 1");    
  while(1)    
  {
       
    printf("I am main thread\n");    
    sleep(1);                                                                                                         
  }    
  return 0;    
}  

編譯執行,本來應該一直迴圈printf列印的主執行緒,因為新執行緒呼叫exit函式導致整個程序終止,所以主執行緒也終止了:

4.1 非主執行緒呼叫return僅終止自己

建立的三個新執行緒都使用return正常退出,在主執行緒使用pthread_join等待並接收他們的退出碼:

#include <stdio.h>                                                                                                  
#include <pthread.h>
#include <unistd.h>

void* Routine(void* arg)
{
   
  for(int i = 0; i < 3; ++i)
  {
   
    printf("I am %s,runing\n", (const char*)arg);
    sleep(1);
  }
  return (void*)123;
}

int main()    
{
       
  pthread_t tid[3];    
  const char* str[3] = {
   "thread 1", "thread 2", "thread 3"};    
  // 1、迴圈建立三個執行緒    
  for(int i = 0; i < 3; ++i)    
  {
       
    pthread_create(&tid[i], NULL, Routine, (void*)str[i]);    
  }    
  // 2、等待三個子執行緒退出    
  for(int i = 0; i < 3; ++i)    
  {
       
    void* status = NULL;    
    pthread_join(tid[i], &status);    
    printf("%s quit,exit code is %d\n", str[i], (int)status);    
    sleep(1);    
  }                                                                                                                 
  return 0;    
}  

編譯執行:

4.2 pthread_cancel()

一般是其他執行緒呼叫這個函式來終止執行緒ID為thread的執行緒,而不是自己呼叫來終止自己。另外被殺死執行緒的退出碼是常數PTHREAD_ CANCELED,即(void*)-1。

返回值:成功返回0,失敗返回錯誤碼。

引數:想要終止的執行緒ID。

函式使用舉例:
建立新三個執行緒,在主執行緒中使用pthread_cancel殺死這個三個執行緒並獲取它們的退出碼:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* Routine(void* arg)
{
   
  while(1)
  {
   
    printf("I am %s,runing\n", (const char*)arg);
    usleep(1000);
  }
  return (void*)123;
}
  
int main()
{
   
  pthread_t tid[3];
  const char* str[3] = {
   "thread 1", "thread 2", "thread 3"};
  // 1、迴圈建立三個執行緒,並使用pthread_cancel殺死建立的三個執行緒
  for(int i = 0; i < 3; ++i)
  {
   
    pthread_create(&tid[i], NULL, Routine, (void*)str[i]);
    pthread_cancel(tid[i]);
  }
  // 2、主執行緒阻塞等待獲取它們的退出碼
  for(int i = 0; i < 3; ++i)
  {
   
    void* status = NULL;
    pthread_join(tid[i], &status);
    printf("%s quit,exit code is %d\n", str[i], (int)status);
  }
  return 0;                                                                                                         
}

編譯執行,發現三個新執行緒的退出碼是常數PTHREAD_ CANCELED,即(void*)-1,而不是return返回的(void*)123。因為這三個新執行緒是被pthread_cancel()終止的,而不是正常順序執行return返回的。

4.3 pthread_exit()

該函式用於執行緒常用於自己終止自己

  • 如果當前執行緒是非分離的,那麼這個執行緒的退出碼retval將被保留,直到其他執行緒用pthread_join來等待當前執行緒終止並獲取到它的退出碼retval。
  • 如果當前執行緒是分離的,退出碼retval將被忽略,該執行緒的所有資源被系統收回。

返回值:無返回值,跟程序一樣,執行緒結束的時候無法返回到它的呼叫者(自身)。

引數:若retval不為空,執行緒的退出碼將被置為retval引數指向的值。

函式使用舉例:
建立三個新執行緒,然後在它們的執行函式裡列印一句話後呼叫pthread_exit自我終止。主執行緒等待並獲取它們的退出碼:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 執行緒printf列印一句話後就呼叫pthread_exit終止自己
void* Routine(void* arg)

{
       
  printf("I am %s,runing\n", (const char*)arg);    
  pthread_exit((void*)123);     
}    
    
int main()    
{
       
  pthread_t tid[3];    
  const char* str[3] = {
   "thread 1", "thread 2", "thread 3"};    
  // 1、迴圈建立三個執行緒    
  for(int i = 0; i < 3; ++i)    
  {
       
    pthread_create(&tid[i], NULL, Routine, (void*)str[i]);    
  }    
  // 2、主執行緒等待並獲取它們的退出碼    
  for(int i = 0; i < 3; ++i)    
  {
       
    void* status = NULL;    
    pthread_join(tid[i], &status);    
    printf("%s quit,exit code is %d\n", str[i], (int)status);    
  }    
  return 0;    
}    

編譯執行:

5. 執行緒分離

預設情況下,新建立的執行緒是不分離的,執行緒退出後,同組的其它執行緒需要對其進行pthread_join操作,否則無法釋放資源,從而導致系統資源洩漏。如果不關心執行緒的返回值,等待就是一種負擔,這個時候,我們可以考慮讓這個執行緒分離,即告訴系統,當這個執行緒退出時,作業系統可以直接回收退出執行緒的資源。

POSIX使用pthread_detach來完成執行緒分離的,它的函式原型如下:

返回值:成功返回0,失敗返回錯誤碼。

引數:要分離執行緒的執行緒ID,可以自己分離自己,也可以分離其他執行緒。

函式說明:
① 可以是執行緒組內其他執行緒對目標執行緒進行分離,也可以是執行緒自己分離。

分離自己:pthread_detach(pthread_self());
分離其他執行緒:pthread_detach(其他執行緒的執行緒ID);

② join和分離是衝突的,不能等待一個已經分離的執行緒。

③ 不能多次呼叫pthread_detach分離同一個執行緒,這樣的結果是不可預見的。

④ 分離的執行緒依然在同一地址空間執行,只不過被分離執行緒的退出狀態不被其他同組執行緒所關心,但如果被分離的執行緒是異常退出,為了安全,作業系統會把整個程序都會銷燬。

函式使用舉例
被分離的執行緒依然和其它同組執行緒共用同一個程序地址空間。分離的意思只是同組的其他執行緒不關心被分離執行緒的死活和它的退出狀態,但如果被分離執行緒異常退出的話,其它同組的所有執行緒也將崩潰。這就好像一個朝廷大臣和家人發生了不可調和的矛盾而分家出去,家人方面是不關心他的死活的。但有一天皇帝發現這個朝廷大臣有通敵罪,為了絕對安全的考慮,把這個朝廷大臣和它的家人全部殺了一樣。

下面程式碼我們在新建執行緒執行函式中把自己分離,然後故意除0使得這個自己這個新執行緒崩潰,觀察主執行緒是否也會跟著一起崩潰:

#include <stdio.h>    
#include <pthread.h>    
    
void* Routine(void* arg)    
{
       
  pthread_detach(pthread_self());    
  printf("I am %s,runing\n", arg);    
  int a = 10/0;    
  pthread_exit((void*)123);    
}    
    
int main()    
{
       
  // 1、建立一個新執行緒    
  pthread_t tid;    
  pthread_create(&tid, NULL, Routine, (void*)"thread 1");    
  // 2、等待新執行緒退出並獲取它的退出碼    
  void* status = NULL;    
  pthread_join(tid, &status);    
  printf("thread 1 quit,exit is:%d", (int)status);                                                                
  return 0;    
} 

編譯執行,發現整個程序都崩潰了:

------------恢復內容結束------------