前言
線程是輕量級的進程(LWP: Light Weight Process
),在Linux
環(huán)境下線程的本質(zhì)仍是進程
,進程是資源分配的最小單位
,線程是操作系統(tǒng)調(diào)度執(zhí)行的最小單位
。
解析 : 進程與線程關(guān)系 :
標(biāo)準(zhǔn)層面 :線程是一個執(zhí)行分支,執(zhí)行粒度比進程更細,調(diào)度成本更低。線程是進程內(nèi)部的一個執(zhí)行流。
內(nèi)核層面 :線程是CPU調(diào)度的基本單位,進程是承擔(dān)分配系統(tǒng)資源的基本實體。
拓展:
需要明確的是,一個進程的創(chuàng)建實際上伴隨著其進程控制塊(task_struct
)、進程地址空間(mm_struct
)以及頁表的創(chuàng)建,虛擬地址和物理地址就是通過頁表建立映射的,如下圖:
每個進程都有自己獨立的進程地址空間和獨立的頁表,也就意味著所有進程在運行時本身就具有獨立性,但如果我們在創(chuàng)建“進程”時,只創(chuàng)建task_struct
,并要求創(chuàng)建出來的task_struct
和父task_struct
共享進程地址空間和頁表,那么創(chuàng)建的結(jié)果就是下面這樣的:
此時創(chuàng)建的實際上就是四個線程:
- 其中每一個線程都是當(dāng)前進程里面的一個執(zhí)行流,也就是我們常說的“線程是進程內(nèi)部的一個執(zhí)行分支”。
- 線程在進程內(nèi)部運行,本質(zhì)就是線程在進程地址空間內(nèi)運行,也就是說曾經(jīng)這個進程申請的所有資源,幾乎都是被所有線程共享的。
解析 :
1、進程內(nèi)的多個線程共享部分
(1) 因為多個線程在同一個地址空間,因此所謂的代碼段(Text Segment)、數(shù)據(jù)段(Data Segment)都是共享的:
- 如果定義一個函數(shù),在各線程中都可以調(diào)用。
- 如果定義一個全局變量,在各線程中都可以訪問到。
(2) 除此之外,各線程還共享以下進程資源和環(huán)境:
- 文件描述符表。(進程打開一個文件后,其他線程也能夠看到)
- 每種信號的處理方式。(SIG_IGN、SIG_QUIT或者自定義的信號處理函數(shù))
- 當(dāng)前工作目錄。(cwd)
- 用戶ID和組ID。
2、多個線程共享進程數(shù)據(jù),但也擁有自己的私有數(shù)據(jù)
讓我們通過一張圖概括線程與進程的包含問題:
因此,所謂的進程并不是通過task_struct
來衡量的,除了task_struct
之外,一個進程還必須要有進程地址空間、文件、信號等等,合起來才能稱之為一個進程。
線程是進程的一個執(zhí)行分支,是在進程內(nèi)部運行的一個執(zhí)行流,是操作系統(tǒng)進行運算調(diào)度的最小單位。線程的執(zhí)行流分為兩種:單執(zhí)行流與多執(zhí)行流,如下圖所示:
這里我們講了很多進程與線程之間的關(guān)系,接下來讓我們一起來了解一些基本概念。
一、基本概念
- 線程就是程序的執(zhí)行路線,它是進程內(nèi)部的控制序列,是資源的調(diào)度單位的基本單位,或者說它是進程的一部分。
- 線程是輕量級的,沒有自己獨立的內(nèi)存資源、代碼段、數(shù)據(jù)區(qū)、堆區(qū)、環(huán)境變量、命令行參數(shù)、文件描述符、信號處理函數(shù)、當(dāng)前目錄等資源。
- 線程擁有自己獨立的棧內(nèi)存,也就是有自己獨立的局部變量,還有獨立的線各ID、錯誤碼、信號掩碼。
- 一個進程可以同時擁有多個線程,也就是擁有多個執(zhí)行路線,其中一個叫主線程。
- 線程是進程的一部分,而且是負責(zé)執(zhí)行的那部分。
二、POSIX線程
POSIX線程(英語:POSIX Threads,常被縮寫為Pthreads)是POSIX的線程標(biāo)準(zhǔn),定義了創(chuàng)建和操縱線程的一套API。
- UNIX和Linux系統(tǒng)早期是沒有線程概念的,線程首先使用在windows系統(tǒng)中,后面發(fā)現(xiàn)線程有它獨特的優(yōu)勢,然后各個廠商各自提供私有的線程庫,而且接口、實現(xiàn)的差異比較大,不易移植。
- 在1995年制定的POSIX標(biāo)準(zhǔn)中,規(guī)定了統(tǒng)一的線程編程接口,遵循POSIX標(biāo)準(zhǔn)的線程實現(xiàn)被統(tǒng)稱為POSIX線程,也叫pthread。
- pthread包含一個頭文件 pthread.h 和一個共享庫 libpthread.so。因此在編譯線程代碼時需要-lpthread參數(shù)。
三、多線程API函數(shù)
1、線程創(chuàng)建
當(dāng)一個程序啟動時,就有一個進程被操作系統(tǒng)創(chuàng)建,與此同時一個線程也立刻運行,這個線程就叫做主線程。
- 主線程是產(chǎn)生其他子線程的線程。
- 通常主線程必須最后完成某些執(zhí)行操作,比如各種關(guān)閉動作。
注意 :每個線程都有唯一的
線程ID
,ID類型為pthread_t
,可理解為:typedef unsigned long int pthread_t;本質(zhì):在Linux下為無符號整數(shù)(%lu),其他系統(tǒng)中可能是結(jié)構(gòu)體實現(xiàn)。多線程庫封裝現(xiàn)成的函數(shù)pthread_t pthread_self(void);
供我們調(diào)用,返回當(dāng)前線程的線程ID,其作用對應(yīng)進程中 getpid() 函數(shù)。
函數(shù)pthread_create用于創(chuàng)建一個線程,詳情如下:
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
參數(shù)說明:
- pthread_t tid:線程id的類型為pthread_t,通常為無符號整型,當(dāng)調(diào)用pthread_create成功時,通過tid指針返回。
- const pthread_attr_t *attr:指定創(chuàng)建線程的屬性,如線程優(yōu)先級、初始棧大小、是否為守護進程等??梢允褂肗ULL來使用默認值,通常情況下我們都是使用默認值。
- void *(*start_routine) (void *):函數(shù)指針start_routine,指定當(dāng)新的線程創(chuàng)建之后,將執(zhí)行的函數(shù)。
- void *arg:線程將執(zhí)行的函數(shù)的參數(shù)。如果想傳遞多個參數(shù),請將它們封裝在一個結(jié)構(gòu)體中。
返回值說明:
- 線程創(chuàng)建成功返回0,失敗返回錯誤碼。
引用頭文件 :#include
編譯方法 :gcc -o <文件名> <文件名.c> -lpthread
問題 :為什么連接線程庫要指明庫名?標(biāo)準(zhǔn)庫不用指明庫名?
- 因為標(biāo)準(zhǔn)庫是語言自帶的,第三方庫不是語言自帶的,可能是系統(tǒng)或者是用戶自己安裝的,線程庫是Linux系統(tǒng)安裝的,不是語言提供的,對于gcc編譯器來說是第三方庫。gcc默認連接庫是標(biāo)準(zhǔn)庫(語言提供的)。編譯器命令行參數(shù)中沒有第三方庫的名字。所以給編譯器指明庫名。
- 強調(diào) :找到庫所在路徑和使用該路徑下的庫文件,是兩碼事。找到路徑找不到庫,還需要指明庫名。標(biāo)準(zhǔn)庫中因為編譯器命令行中有該庫名。
1)無參數(shù)線程函數(shù)創(chuàng)建線程
創(chuàng)建一個線程,線程執(zhí)行的函數(shù)的參數(shù)為NULL,源代碼如下:
#include < pthread.h >
#include < stdio.h >
void* thfunc(void *arg)
{
printf("in thfuncn");
return (void*)0;
}
int main(int argc, char* argv[])
{
pthread_t pid;
int ret;
ret = pthread_create(&pid,NULL,thfunc,NULL);
if(ret)
{
printf("pthread_create fail:%dn",ret);
return -1;
}
sleep(1);
printf("in main:thread is created.n");
return 0;
}
編譯源文件,生成可執(zhí)行文件,運行如下:
[root@localhost home]# gcc -o phtread_test phtread_test.c -pthread
[root@localhost home]#
[root@localhost home]# ls
master phtread_test phtread_test.c
[root@localhost home]# ./phtread_test
in thfunc
in main:thread is created.
2)帶參數(shù)線程函數(shù)創(chuàng)建線程
創(chuàng)建一個線程,并傳遞結(jié)構(gòu)體作為參數(shù),源代碼如下:
#include < pthread.h >
#include < stdio.h >
#include < stdlib.h >
#include < unistd.h >
typedef struct student{
int score;
char *name;
}Student;
void *thfunc(void *arg){ //線程函數(shù)
Student *p = (Student*)arg;
printf("in thfunc:name=%s,core=%dn",p- >name,p- >score);//輸出結(jié)構(gòu)體內(nèi)容
return (void *)0;
}
int main()
{
pthread_t tid;
int ret;
Student stu;
stu.score = 90;
stu.name = "Qin";
ret = pthread_create(&tid, NULL, thfunc, (void *)&stu); //創(chuàng)建線程
if(ret){
printf("pthread_creat failed:%dn", ret);
return -1;
}
//用于等待某個線程退出,成功返回0,否則返回Exxx(為正數(shù))
pthread_join(tid, NULL);
printf("in main:thread is createdn");
return 0;
}
編譯源文件,生成可執(zhí)行文件,運行如下:
[root@localhost 0629]# gcc -o pthread_test_arg pthread_test_arg.c -lpthread
[root@localhost 0629]# ls
pthread_test_arg pthread_test_arg.c
[root@localhost 0629]# ./pthread_test_arg
in thfunc:name=Qin,core=90
in main:thread is created
3)創(chuàng)建一個線程,共享進程數(shù)據(jù)
#include < pthread.h >
#include < stdio.h >
#include < stdlib.h >
#include < unistd.h >
int global=100;
void *thfunc(void *arg){ //線程函數(shù)
global++;
printf("in thfunc:global=%d,n",global);
return (void*)0;
}
int main()
{
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, thfunc, NULL); //創(chuàng)建線程
if(ret){
printf("pthread_creat failed:%dn", ret);
return -1;
}
pthread_join(tid, NULL); //等待子線程結(jié)束
global++;
printf("in main:global is:%dn",global);
return 0;
}
編譯源文件,生成可執(zhí)行文件,運行如下:
[root@localhost share]# gcc -o pthread_test_share pthread_test_share.c -lpthread
[root@localhost share]# ./pthread_test_share
in thfunc:global=101,
in main:global is:102
2、線程等待
Linux平臺的pthread有兩種狀態(tài):joinable狀態(tài)和unjoinable狀態(tài):
- joinable狀態(tài)的線程,當(dāng)線程函數(shù)自己返回退出時或調(diào)用
pthread_exit
時都不會釋放線程所占用堆棧和線程描述符。只有當(dāng)你調(diào)用了pthread_join
之后這些資源才會被釋放,因此,需要main函數(shù)或者其他線程去調(diào)用pthread_join
函數(shù)。- unjoinable狀態(tài)的線程,這些資源在線程函數(shù)退出時或調(diào)用
pthread_exit
時會自動被釋放。設(shè)置unjoinable狀態(tài)設(shè)置有兩種辦法:一是在調(diào)用pthread_create
時指定;二是子線程創(chuàng)建后在子線程中調(diào)用pthread_detach(pthread_self())
分離線程自身,狀態(tài)改為unjoinable狀態(tài),確保資源自動的釋放。
一個線程被創(chuàng)建出來默認的狀態(tài)是joinable
,如果一個線程結(jié)束運行但沒有被join
,則它的狀態(tài)類似于進程中的僵尸進程,即還有一部分資源沒有被回收(退出狀態(tài)碼)。所以創(chuàng)建線程者應(yīng)該調(diào)用pthread_join
來等待線程運行結(jié)束,并可得到線程的退出代碼,回收其資源(類似于wait,waitpid
) 。如果創(chuàng)建線程者不對新線程進行等待,那么這個新線程的資源也是不會被回收的,就會產(chǎn)生類似于“僵尸進程”的問題,也就是內(nèi)存泄漏。
函數(shù)pthread_join
用來等待子線程結(jié)束,函數(shù)會讓主線程掛起(即休眠,讓出CPU
),直到子線程退出,pthread_join
能讓子線程所占資源釋放。如果子線程已經(jīng)結(jié)束,那么該函數(shù)會立即返回。
int pthread_join(pthread_t tid,void **retval);
參數(shù)說明:
- tid:被等待線程的ID。
- retval:retval通常設(shè)為NULL,如果不為NULL,返回被等待線程的返回值。
返回值說明:
- 線程等待成功返回0,失敗返回錯誤碼。
引用頭文件 :#include
注意:
pthread_join
函數(shù)默認是以阻塞的方式進行線程等待的,并且指定的線程必須是joinable的。。
如下兩個Demo演示了線程創(chuàng)建出來后,沒有被join
和被join
的場景,即沒有調(diào)用pthread_join
等待子線程運行結(jié)束和調(diào)用pthread_join
等待子線程運行結(jié)束,讓我們對比看一下會發(fā)生什么。
Demo1 : 使用默認狀態(tài),注釋掉pthread_join函數(shù)
pthread_join_default源文件如下:
#include < stdlib.h >
#include < unistd.h >
#include < stdio.h >
#include < pthread.h >
void *thread_function(void *arg)
{
int i;
for (i = 0; i < 8; i++)
{
printf("%s:Thread working...! %d n", __FUNCTION__, i);
sleep(1);
}
return NULL;
}
int main(void)
{
pthread_t mythread;
if (pthread_create(&mythread, NULL, thread_function, NULL))
{
printf("error creating thread.");
abort();
}
/*
if ( pthread_join ( mythread, NULL ) )
{
printf("error join thread.");
abort();
}
*/
printf("%s:Thread done! n", __FUNCTION__);
exit(0);
}
使用gcc編譯源文件,注意編譯多線程的程序,要在gcc命令尾部加上-lpthread,鏈接pthread庫。運行可執(zhí)行程序,如下:
[root@localhost 77]# gcc -o pthread_join_default pthread_join_default.c -lpthread
[root@localhost 77]# ./pthread_join_default
main:Thread done!
根據(jù)結(jié)果可知,子線程的內(nèi)容未被打印出來,因為主線程main 跑的比子線程快,子線程中 thread_function
的打印是不會打印出來的,其所占用的資源也未被回收,也就不能被復(fù)用, 這就造成了資源的泄漏。
Demo2: 使用pthread_join 阻塞主線程
pthread_join()使得子線程合入主線程,主線程阻塞等待子線程結(jié)束,然后回收子線程資源。
將上述Demo 1 中的注釋放開,修改后的pthread_join_default源文件如下:
#include < stdlib.h >
#include < unistd.h >
#include < stdio.h >
#include < pthread.h >
void *thread_function(void *arg)
{
int i;
for (i = 0; i < 8; i++)
{
printf("%s:Thread working...! %d n", __FUNCTION__, i);
sleep(1);
}
return NULL;
}
int main(void)
{
pthread_t mythread;
if (pthread_create(&mythread, NULL, thread_function, NULL))
{
printf("error creating thread.");
abort();
}
if ( pthread_join ( mythread, NULL ) )
{
printf("error join thread.");
abort();
}
printf("%s:Thread done! n", __FUNCTION__);
exit(0);
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 77]# gcc -o pthread_join_default pthread_join_default.c -lpthread
[root@localhost 77]# ./pthread_join_default
thread_function:Thread working...! 0
thread_function:Thread working...! 1
thread_function:Thread working...! 2
thread_function:Thread working...! 3
thread_function:Thread working...! 4
thread_function:Thread working...! 5
thread_function:Thread working...! 6
thread_function:Thread working...! 7
main:Thread done!
可以看到輸出結(jié)果中子線程 thread_function
的打印。因此,使用pthread_join
函數(shù)不僅可以使子線程的資源被回收,也可以使子線程的邏輯流被執(zhí)行到,從而發(fā)揮想要實現(xiàn)的作用。
通過pthread_join
得到的終止?fàn)顟B(tài)是不同的。線程退出和進程退出一樣,有三種狀態(tài):
- 代碼正常運行,結(jié)果正確,正常退出
- 代碼正常運行,結(jié)果不正確,不正常退出
- 代碼出現(xiàn)異常,異常退出
提示 :前兩種情況以退出碼來表述退出情況,后面一種以退出信號來表示
問題 :但是線程等待函數(shù)的第2個參數(shù)返回的是被等待線程執(zhí)行函數(shù)的返回值,也就是退出碼,沒有表示線程異常退出的情況,這是為什么?
答 :因為某個線程如果運行異常終止,整個進程都會終止。進程異常終止,就屬于進程的等待處理的范疇了,不屬于線程范疇。比如:一個線程函數(shù)有除0操作,硬件MMU發(fā)現(xiàn)異常,操作系統(tǒng)收到異常,向該進程發(fā)出信號,終止進程。信號處理的單位是進程。
總的來說就是,被等待線程只關(guān)心正常運行的退出情況,獲取退出碼。不關(guān)心異常退出情況,異常退出情況上升至進程處理范疇。
問題 :這里我們怎么獲取退出碼呢?
答 :調(diào)用pthread_join函數(shù)的線程默認以阻塞方式等待線程id為tid參數(shù)的線程終止,線程以不同的方式終止,得到的終止?fàn)顟B(tài)不同:
- 如果線程通過return終止,pthread_join函數(shù)的第二個參數(shù)retval直接指向return后面的返回值;
- 如果線程通過pthread_exit終止,pthread_join函數(shù)的第二個參數(shù)retval直接指向pthread_exit參數(shù)‘;
- 如果線程通過被其它線程調(diào)用pthread_cancel終止,pthread_join函數(shù)的第二個參數(shù)retval直接存放的是一個常數(shù)宏P(guān)THREAD_CANCELED,值是-1。
#define PTHREAD_CANCELED (void *)-1
;- 如果不關(guān)心返回值,可以將ret_val設(shè)為NULL。
接下來讓我們一起用實例分別說明正常退出和異常退出狀態(tài),其中正常退出分為return、pthread_exit、pthread_cancel三種方式,異常退出則會直接終止進程。pthread_exit、pthread_cancel的詳細介紹見下文。
1)正常退出
(1)return
pthread_test.c源文件如下:
#include < stdio.h >
#include < unistd.h >
#include < pthread.h >
void *thread1(void *arg)
{
int num=0;
while(1){
sleep(1);
num++;
printf("thread1 pid:%d, tid:%lun",getpid(),*(pthread_t*)arg);
if(num==5) break;
}
//退出該線程
return (void*)1;
}
int main()
{
pthread_t tid;
//創(chuàng)建線程
pthread_create(&tid,NULL,thread1,(void*)&tid);
printf("main pid:%d, tid:%lun",getpid(),tid);
//保存退出碼
void* ret=NULL;
//等待線程退出
pthread_join(tid,&ret);
printf("thread1 is quit,quit code is %dn",(unsigned long)ret);
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 77]# gcc -o pthread_test pthread_test.c -lpthread
[root@localhost 77]# ./pthread_test
main pid:5934, tid:139685158942464
thread1 pid:5934, tid:139685158942464
thread1 pid:5934, tid:139685158942464
thread1 pid:5934, tid:139685158942464
thread1 pid:5934, tid:139685158942464
thread1 pid:5934, tid:139685158942464
thread1 is quit,quit code is 1
根據(jù)程序運行結(jié)果可知,被等待線程返回退出碼1且其第二個參數(shù)ret收到返回值1。證明pthread_join函數(shù)的第二個參數(shù)ret直接指向return后面的返回值。
注意 :在線程中使用return代表當(dāng)前線程退出,但是在main函數(shù)中使用return代表整個進程退出,也就是說只要主線程退出了那么整個進程就退出了,此時該進程曾經(jīng)申請的資源就會被釋放,而其他線程會因為沒有了資源,自然而然的也退出了。
問題1 :為什么printf中直接使用ret?
答:因為返回值是void*, ret也是void*, 用ret接受的返回值。
問題2 :為什么強轉(zhuǎn)為unsigned long?
答:因為void*是指針,指針在32位CPU下是32位尋址空間,即4字節(jié);在64位CPU是64位尋址空間,即8字節(jié)。如果強轉(zhuǎn)成int類型可能會截斷,從而導(dǎo)致數(shù)據(jù)錯誤。而unsigned long一般跟CPU的可尋址范圍的字節(jié)長度是相等的,所以Linux內(nèi)核中有很多代碼都是用unsigned long來替代指針,用unsigned long表示的地址一般不允許引用該地址所在的內(nèi)存,如果要強制引用或者訪問這片內(nèi)存,則需要在unsigned long前面加上強制指針轉(zhuǎn)換 (char *)unsigned long。
看到這里,可能有的朋友比較疑惑,指針大小自己記得好像是和操作系統(tǒng)位數(shù)有關(guān)系的,怎么這里卻說是和CPU位數(shù)有關(guān)系呢,這里不得不拓展一點小知識。
拓展 :指針的大小究竟和什么有關(guān)系?
解析:指針的大小和下面這3種均有關(guān)系,當(dāng)下述3種位數(shù)不同時,取最小的位數(shù)。
- cpu位數(shù)(32位數(shù)4字節(jié),64位數(shù)8字節(jié))
- 操作系統(tǒng)位數(shù)(32位數(shù)4字節(jié),64位數(shù)8字節(jié))
- 編譯器的位數(shù)(32位數(shù)4字節(jié),64位數(shù)8字節(jié))
比如,如果CPU、系統(tǒng)都是64位的,但編譯器是32位的,那么很顯然指針只能是32位,即4字節(jié)大小。
(2)pthread_exit
在原pthread_test.c源文件基礎(chǔ)上,修改如下:
#include < stdio.h >
#include < unistd.h >
#include < pthread.h >
void *thread1(void *arg)
{
int num=0;
while(1){
sleep(1);
num++;
printf("thread1 pid:%d, tid:%lun",getpid(),*(pthread_t*)arg);
if(num==5) break;
}
//退出該線程
//return (void*)1;
pthread_exit((void*)1);
}
int main()
{
pthread_t tid;
//創(chuàng)建線程
pthread_create(&tid,NULL,thread1,(void*)&tid);
printf("main pid:%d, tid:%lun",getpid(),tid);
//保存退出碼
void* ret=NULL;
//等待線程退出
pthread_join(tid,&ret);
printf("thread1 is quit,quit code is %dn",(unsigned long)ret);
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 77]# gcc -o pthread_test pthread_test.c -lpthread
[root@localhost 77]# ./pthread_test
main pid:7392, tid:140455491106560
thread1 pid:7392, tid:140455491106560
thread1 pid:7392, tid:140455491106560
thread1 pid:7392, tid:140455491106560
thread1 pid:7392, tid:140455491106560
thread1 pid:7392, tid:140455491106560
thread1 is quit,quit code is 1
根據(jù)程序運行結(jié)果可知,被等待線程返回退出碼同上。證明pthread_join函數(shù)的第二個參數(shù)ret直接指向pthread_exit后面的返回值。
注意: exit函數(shù)的作用是終止進程,任何一個線程調(diào)用exit函數(shù)也代表的是整個進程終止。
(3)pthread_cancel
在(2)的pthread_test.c源文件基礎(chǔ)上,修改如下:
#include < stdio.h >
#include < unistd.h >
#include < pthread.h >
void *thread1(void *arg)
{
int num=0;
while(1){
sleep(1);
num++;
printf("thread1 pid:%d, tid:%lun",getpid(),*(pthread_t*)arg);
if(num==5) break;
}
//退出該線程
//return (void*)1;
//pthread_exit((void*)1);
}
int main()
{
pthread_t tid;
//創(chuàng)建線程
pthread_create(&tid,NULL,thread1,(void*)&tid);
printf("main pid:%d, tid:%lun",getpid(),tid);
sleep(5);
//退出線程ID為tid的線程
pthread_cancel(tid);
//保存退出碼
void* ret=NULL;
//等待線程退出
pthread_join(tid,&ret);
printf("thread1 is quit,quit code is %dn",(unsigned long)ret);
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 77]# gcc -o pthread_test pthread_test.c -lpthread
[root@localhost 77]# ./pthread_test
main pid:8221, tid:139941163689728
thread1 pid:8221, tid:139941163689728
thread1 pid:8221, tid:139941163689728
thread1 pid:8221, tid:139941163689728
thread1 pid:8221, tid:139941163689728
thread1 is quit,quit code is -1
根據(jù)程序運行結(jié)果可知,被等待線程返回值是-1,證明pthread_join函數(shù)的第二個參數(shù)ret直接存放的是常數(shù)宏P(guān)THREAD_CANCELED的值。
2)異常退出
修改pthread_test.c源文件,如下:
#include < stdio.h >
#include < unistd.h >
#include < pthread.h >
void *thread1(void *arg)
{
int num=0;
int b;
b/=0;
while(1){
sleep(1);
num++;
printf("thread1 pid:%d, tid:%lun",getpid(),*(pthread_t*)arg);
if(num==5) break;
}
//退出該線程
return (void*)1;
//pthread_exit((void*)1);
}
int main()
{
pthread_t tid;
//創(chuàng)建線程
pthread_create(&tid,NULL,thread1,(void*)&tid);
printf("main pid:%d, tid:%lun",getpid(),tid);
sleep(5);
//保存退出碼
void* ret=NULL;
//等待線程退出
pthread_join(tid,&ret);
printf("thread1 is quit,quit code is %dn",(unsigned long)ret);
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 77]# gcc -o pthread_test pthread_test.c -lpthread
pthread_test.c: 在函數(shù)‘thread1’中:
pthread_test.c:9:5: 警告:被零除 [-Wdiv-by-zero]
b/=0;
^
[root@localhost 77]# ls
pthread_test pthread_test.c
[root@localhost 77]# ./pthread_test
main pid:8594, tid:140524144858880
浮點數(shù)異常(吐核)
根據(jù)程序運行結(jié)果可知,并沒有收集到退出碼,進程意外終止了。
3、線程退出
調(diào)用線程退出函數(shù),當(dāng)前線程會馬上退出,不會影響其他線程的正常運行,不管是在子線程和主線程都可以使用。pthread_exit()函數(shù)一般用于線程主動終止自身的情景下。
void pthread_exit(void* retval);
參數(shù)說明:
- retval: 線程退出時攜帶的數(shù)據(jù)(函數(shù)返回值),當(dāng)前子線程會得到該數(shù)據(jù),相當(dāng)于return (void*) retval,若不需要,指定為NULL。
引用頭文件 :#include
線程退出函數(shù),重點需要關(guān)注的就是回收子線程數(shù)據(jù),常用的做法是在子線程退出的時候使用pthread_exit()
的參數(shù)將數(shù)據(jù)傳出,在回收這個子線程的時候通過pthread_join()
的第二個參數(shù)來接受子線程傳遞出的數(shù)據(jù)。
注意 :使用return和pthread_exit返回的指針?biāo)赶虻膬?nèi)存單元必須是全局或者是malloc分配的,不能是在線程函數(shù)棧上分配的,因為線程退出時,函數(shù)棧幀被釋放了。
如下讓我們通過示例來說明回收子線程數(shù)據(jù)時,錯誤的使用方法和正確的使用方法。
1)返回指向線程函數(shù)棧上分配的變量的指針
pthread_join_test.c源文件:
#include < stdio.h >
#include < stdlib.h >
#include < unistd.h >
#include < string.h >
#include < pthread.h >
// 定義結(jié)構(gòu)體
struct Persion
{
int id;
char name[36];
int age;
};
// 子線程的處理代碼
void* working(void* arg)
{
printf("我是子線程, 線程ID: %ldn", pthread_self());
int i;
for(i=0; i< 9; ++i)
{
printf("child == i: = %dn", i);
if(i == 6)
{
struct Persion p;
p.age = 12;
strcpy(p.name, "Alex");
p.id = 100;
// 該函數(shù)的參數(shù)將這個地址傳遞給了主線程的pthread_join()
pthread_exit(&p);
}
}
return NULL; // 代碼執(zhí)行不到這個位置就退出了
}
int main()
{
// 1. 創(chuàng)建一個子線程
pthread_t tid;
pthread_create(&tid, NULL, working, NULL);
printf("子線程創(chuàng)建成功, 線程ID: %ldn", tid);
// 2. 子線程不會執(zhí)行下邊的代碼, 主線程執(zhí)行
printf("我是主線程, 線程ID: %ldn", pthread_self());
int i;
for(i=0; i< 3; ++i)
{
printf("i = %dn", i);
}
// 阻塞等待子線程退出
void* ptr = NULL;
// ptr是一個傳出參數(shù), 在函數(shù)內(nèi)部讓這個指針指向一塊有效內(nèi)存
// 這個內(nèi)存地址就是pthread_exit() 參數(shù)指向的內(nèi)存
pthread_join(tid, &ptr);
// 打印信息
struct Persion* pp = (struct Persion*)ptr;
printf("子線程返回數(shù)據(jù): name: %s, age: %d, id: %dn", pp- >name, pp- >age, pp- >id);
printf("子線程資源被成功回收...n");
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 78]# gcc -o pthread_join_test pthread_join_test.c -lpthread
[root@localhost 78]#
[root@localhost 78]# ./pthread_join_test
子線程創(chuàng)建成功, 線程ID: 139867083597568
我是主線程, 線程ID: 139867091879744
i = 0
i = 1
i = 2
我是子線程, 線程ID: 139867083597568
child == i: = 0
child == i: = 1
child == i: = 2
child == i: = 3
child == i: = 4
child == i: = 5
child == i: = 6
子線程返回數(shù)據(jù): name: , age: 1481975384, id: 4
子線程資源被成功回收...
根據(jù)上述結(jié)果可知,回收子線程數(shù)據(jù)錯誤,因為子線程退出時,函數(shù)棧幀被釋放了,因此在函數(shù)中定義的局部結(jié)構(gòu)體變量中的值就變成了隨機值,所以在回收子線程數(shù)據(jù)時,絕對不能使用局部變量,必須使用全局變量或者malloc分配的變量。
2)返回指向全局變量的指針
基于pthread_join_test.c修改后的源文件如下:
#include < stdio.h >
#include < stdlib.h >
#include < unistd.h >
#include < string.h >
#include < pthread.h >
// 定義結(jié)構(gòu)體
struct Persion
{
int id;
char name[36];
int age;
};
//定義全局結(jié)構(gòu)體
struct Persion p;
// 子線程的處理代碼
void* working(void* arg)
{
printf("我是子線程, 線程ID: %ldn", pthread_self());
int i;
for(i=0; i< 9; ++i)
{
printf("child == i: = %dn", i);
if(i == 6)
{
p.age = 12;
strcpy(p.name, "Alex");
p.id = 100;
// 該函數(shù)的參數(shù)將這個地址傳遞給了主線程的pthread_join()
pthread_exit(&p);
}
}
return NULL; // 代碼執(zhí)行不到這個位置就退出了
}
int main()
{
// 1. 創(chuàng)建一個子線程
pthread_t tid;
pthread_create(&tid, NULL, working, NULL);
printf("子線程創(chuàng)建成功, 線程ID: %ldn", tid);
// 2. 子線程不會執(zhí)行下邊的代碼, 主線程執(zhí)行
printf("我是主線程, 線程ID: %ldn", pthread_self());
int i=0;
for(; i< 3; ++i)
{
printf("i = %dn", i);
}
// 阻塞等待子線程退出
void* ptr = NULL;
// ptr是一個傳出參數(shù), 在函數(shù)內(nèi)部讓這個指針指向一塊有效內(nèi)存
// 這個內(nèi)存地址就是pthread_exit() 參數(shù)指向的內(nèi)存
pthread_join(tid, &ptr);
// 打印信息
struct Persion* pp = (struct Persion*)ptr;
printf("子線程返回數(shù)據(jù): name: %s, age: %d, id: %dn", pp- >name, pp- >age, pp- >id);
printf("子線程資源被成功回收...n");
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 78]# gcc -o pthread_join_test pthread_join_test.c -lpthread
[root@localhost 78]# ./pthread_join_test
子線程創(chuàng)建成功, 線程ID: 140373544281856
我是主線程, 線程ID: 140373552564032
i = 0
i = 1
i = 2
我是子線程, 線程ID: 140373544281856
child == i: = 0
child == i: = 1
child == i: = 2
child == i: = 3
child == i: = 4
child == i: = 5
child == i: = 6
子線程返回數(shù)據(jù): name: Alex, age: 12, id: 100
子線程資源被成功回收...
根據(jù)上述結(jié)果可知,回收子線程數(shù)據(jù)正確,因為子線程函數(shù)中使用了指向全局變量的指針傳遞返回值,因此在線程退出時,該塊內(nèi)存地址未被釋放,因此主線程中可以成功訪問該返回值。
4、線程取消
線程是可以被取消的,我們可以使用pthread_cancel函數(shù)取消某一個線程,pthread_cancel函數(shù)的函數(shù)原型如下:
int pthread_cancel(pthread_t tid);
參數(shù)說明:
- tid:被取消線程的ID。
返回值說明:
- 線程取消成功返回0,失敗返回錯誤碼。
引用頭文件 :#include
線程是可以取消自己的,取消成功的線程的退出碼一般是-1。雖然線程可以自己取消自己,但一般不這樣做,我們往往是用于一個線程取消另一個線程,比如主線程取消新線程。
這里我們通過一個demo來演示,pthread_cancel_test.c源文件如下:
#include< stdio.h >
#include < stdlib.h >
#include< pthread.h >
#include< unistd.h >
void* Routine(void* arg)
{
char* msg = (char*)arg;
int count = 0;
while (count < 5){
printf("I am %s...pid: %d, ppid: %d, tid: %lun", msg, getpid(), getppid(), pthread_self());
sleep(1);
count++;
}
pthread_exit((void*)6666);
}
int main()
{
pthread_t tid[5];
int i;
for (i = 0; i < 5; i++){
char* buffer = (char*)malloc(64);
sprintf(buffer, "thread %d", i);
pthread_create(&tid[i], NULL, Routine, buffer);
printf("%s tid is %lun", buffer, tid[i]);
}
pthread_cancel(tid[0]);
pthread_cancel(tid[1]);
pthread_cancel(tid[2]);
pthread_cancel(tid[3]);
printf("I am main thread...pid: %d, ppid: %d, tid: %lun", getpid(), getppid(), pthread_self());
for (i = 0; i < 5; i++){
void* ret = NULL;
pthread_join(tid[i], &ret);
printf("thread %d[%lu]...quit, exitcode: %dn", i, tid[i], ret);
}
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 78]# gcc -o pthread_cancel_test pthread_cancel_test.c -lpthread
[root@localhost 78]# ./pthread_cancel_test
thread 0 tid is 139921746663168
thread 1 tid is 139921738270464
thread 2 tid is 139921729877760
thread 3 tid is 139921721485056
thread 4 tid is 139921713092352
I am main thread...pid: 17667, ppid: 3835, tid: 139921754945344
I am thread 0...pid: 17667, ppid: 3835, tid: 139921746663168
thread 0[139921746663168]...quit, exitcode: -1
I am thread 4...pid: 17667, ppid: 3835, tid: 139921713092352
I am thread 3...pid: 17667, ppid: 3835, tid: 139921721485056
I am thread 2...pid: 17667, ppid: 3835, tid: 139921729877760
I am thread 1...pid: 17667, ppid: 3835, tid: 139921738270464
thread 1[139921738270464]...quit, exitcode: -1
thread 2[139921729877760]...quit, exitcode: -1
thread 3[139921721485056]...quit, exitcode: -1
I am thread 4...pid: 17667, ppid: 3835, tid: 139921713092352
I am thread 4...pid: 17667, ppid: 3835, tid: 139921713092352
I am thread 4...pid: 17667, ppid: 3835, tid: 139921713092352
I am thread 4...pid: 17667, ppid: 3835, tid: 139921713092352
thread 4[139921713092352]...quit, exitcode: 6666
此時可以發(fā)現(xiàn),0、1、2、3號線程退出時的退出碼不是我們設(shè)置的6666,而是-1,只有未被取消的4號線程的退出碼是6666,因為只有4號進程未被取消。 此外, 新線程也是可以取消主線程的 。
5、線程分離
我們知道Linux下線程默認的狀態(tài)是joinable
,如果一個線程結(jié)束運行但沒有被join
,則它的狀態(tài)類似于進程中的Zombie Process
,即還有一部分資源沒有被回收(退出狀態(tài)碼)。因此,創(chuàng)建線程者會調(diào)用pthread_join
來等待線程運行結(jié)束,并得到線程的退出代碼,回收其資源。但是調(diào)用pthread_join(pthread_id)
后,如果該線程沒有運行結(jié)束,調(diào)用者會被阻塞,在有些情況下我們并不希望如此。
比如,在Web服務(wù)器中當(dāng)主線程為每個新來的鏈接創(chuàng)建一個子線程進行處理的時候,主線程并不希望因為調(diào)用pthread_join
而阻塞(因為還要繼續(xù)處理之后到來的鏈接),這時可以在子線程中加入代碼pthread_detach(pthread_self())
或者在父線程中調(diào)用pthread_detach(thread_id)
(非阻塞,可立即返回),這會將該子線程的狀態(tài)設(shè)置為detached
,則該子線程運行結(jié)束后會自動釋放所有資源。
int pthread_detach(pthread_t tid);
參數(shù)說明:
- tid:被分離線程的ID。
返回值說明:
- 成功返回0,失敗返回錯誤碼。
引用頭文件 :#include
注意:可以是線程組內(nèi)其它線程對目標(biāo)線程分離,也可以是線程分離自己
如果不關(guān)心返回值,我們可以告訴系統(tǒng),將線程分離,當(dāng)線程退出后,將自動釋放線程資源 ,如下演示創(chuàng)建新線程的線程,不關(guān)心新線程的返回值,使用線程分離。
pthread_test.c源文件如下:
#include < stdio.h >
#include < unistd.h >
#include < pthread.h >
void *thread1(void *arg)
{
//分離該線程
pthread_detach(pthread_self());
sleep(1);
printf("thread1 pid:%d, tid:%lun",getpid(),*(pthread_t*)arg);
//退出該線程
return (void*)1;
//pthread_exit((void*)1);
}
int main()
{
pthread_t tid;
//創(chuàng)建線程
pthread_create(&tid,NULL,thread1,(void*)&tid);
printf("main pid:%d, tid:%lun",getpid(),tid);
sleep(5);
//保存退出碼
void* ret=NULL;
//等待線程退出
pthread_join(tid,&ret);
printf("thread1 is quit,quit code is %dn",(unsigned long)ret);
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 77]# gcc -o pthread_test pthread_test.c -lpthread
[root@localhost 77]# ./pthread_test
main pid:25180, tid:139943567070976
thread1 pid:25180, tid:139943567070976
thread1 is quit,quit code is 0
新線程返回值是1,但是實際返回的是0,說明沒有收到返回值,因為ret本來就是NULL。
雖然線程分離了,但是當(dāng)分離的線程因為異常終止,依然會導(dǎo)致進程終止,接下來讓我們通過示例來說明。
pthread_test.c源文件修改如下:
#include < stdio.h >
#include < unistd.h >
#include < pthread.h >
void *thread1(void *arg)
{
//分離該線程
pthread_detach(pthread_self());
sleep(1);
printf("thread1 pid:%d, tid:%lun",getpid(),*(pthread_t*)arg);
int b;
b/=0;
//退出該線程
return (void*)1;
//pthread_exit((void*)1);
}
int main()
{
pthread_t tid;
//創(chuàng)建線程
pthread_create(&tid,NULL,thread1,(void*)&tid);
printf("main pid:%d, tid:%lun",getpid(),tid);
sleep(5);
//保存退出碼
void* ret=NULL;
//等待線程退出
pthread_join(tid,&ret);
printf("thread1 is quit,quit code is %dn",(unsigned long)ret);
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 77]# gcc -o pthread_test pthread_test.c -lpthread
pthread_test.c: 在函數(shù)‘thread1’中:
pthread_test.c:12:6: 警告:被零除 [-Wdiv-by-zero]
b/=0;
^
[root@localhost 77]# ./pthread_test
main pid:27154, tid:139621331527424
thread1 pid:27154, tid:139621331527424
浮點數(shù)異常(吐核)
根據(jù)上述結(jié)果可知,因為線程異常從而導(dǎo)致進程被終止。
線程設(shè)置為分離狀態(tài)時,線程主動與主控線程斷開關(guān)系。線程結(jié)束后(不會產(chǎn)生僵尸線程),其退出狀態(tài)不由其他線程獲取,而直接自己自動釋放(自己清理掉PCB的殘留資源)。網(wǎng)絡(luò)、多線程服務(wù)器常用。
進程若有該機制,將不會產(chǎn)生僵尸進程。僵尸進程的產(chǎn)生主要由于進程死后,大部分資源被釋放,一點殘留資源仍存于系統(tǒng)中,導(dǎo)致內(nèi)核認為該進程仍存在。( 注意進程沒有這一機制 ) 一般情況下,線程終止后,其終止?fàn)顟B(tài)一直保留到其它線程調(diào)用pthread_join獲取它的狀態(tài)為止(或者進程終止被回收了)。但是線程也可以被置為detach狀態(tài),這樣的線程一旦終止就立刻回收它占用的所有資源,而不保留終止?fàn)顟B(tài)。將上述pthread_test.c源文件稍作修改如下:
#include < stdio.h >
#include < string.h >
#include < unistd.h >
#include < pthread.h >
void *thread1(void *arg)
{
//分離該線程
pthread_detach(pthread_self());
sleep(1);
printf("thread1 pid:%d, tid:%lun",getpid(),*(pthread_t*)arg);
//退出該線程
//return (void*)1;
pthread_exit((void*)1);
}
int main()
{
pthread_t tid;
int err;
//創(chuàng)建線程
pthread_create(&tid,NULL,thread1,(void*)&tid);
printf("main pid:%d, tid:%lun",getpid(),tid);
sleep(5);
//保存退出碼
void* ret=NULL;
//等待線程退出
err = pthread_join(tid, &ret);
printf("-------------err= %dn", err);
if (err != 0)
fprintf(stderr, "thread_join error: %sn", strerror(err));
else
fprintf(stderr, "thread exit code %dn", (unsigned long)ret);
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 77]# ./pthread_test
main pid:28886, tid:140684657252096
thread1 pid:28886, tid:140684657252096
-------------err= 22
thread_join error: Invalid argument
上述結(jié)果表明不能對一個已經(jīng)處于detach狀態(tài)的線程調(diào)用pthread_join,這樣的調(diào)用將返回EINVAL錯誤(22號錯誤)。也就是說,如果已經(jīng)對一個線程調(diào)用了pthread_detach就不能再調(diào)用pthread_join了。
6、線程信號
別被名字嚇到,pthread_kill可不是kill,而是向線程發(fā)送一個signal。還記得signal嗎,大部分signal的默認動作是終止進程的運行,所以,我們才要用signal()去抓信號并加上處理函數(shù)。
int pthread_kill(thread_t tid, int sig);
參數(shù)說明:
- tid: 要發(fā)送信號的線程。
- sig: 要發(fā)送的信號,0是保留信號,用來判斷線程是否還存在。
返回值說明:
- 成功: 返回0。
- 線程不存在:返回ESRCH。
- 信號不合法:返回EINVAL。
引用頭文件 :#include
向指定tid的線程發(fā)送sig信號,如果線程代碼內(nèi)不做處理,則按照信號默認的行為影響整個進程,也就是說,如果你給一個線程發(fā)送了SIGQUIT,但線程卻沒有實現(xiàn)signal處理函數(shù),則整個進程退出。
由于pthread_t類型的線程ID只在線程組內(nèi)是唯一的,其他進程完全可能存在線程ID相同的線程,所以pthread_kill只能向同一個進程的線程發(fā)送信號。
接下來通過示例演示pthread_kill的用法,pthread_kill_test.c源文件如下:
#include < stdio.h >
#include < pthread.h >
#include < signal.h >
#include < unistd.h >
#include < errno.h >
void *thrfunc(void *arg){
int count = 50;
while(1){
printf("thread work:threadID=%d, count=%dn",pthread_self(),count);
sleep(1);
count--;
}
return (void*)0;
}
int main(){
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, thrfunc, NULL);
sleep(5);
printf("main thread:threadID=%dn",pthread_self());
int kill_rc = pthread_kill(tid,0);
if(kill_rc == ESRCH)
printf("the specified thread did not exists or already quitn");
else if(kill_rc == EINVAL)
printf("signal is invalidn");
else
printf("the specified thread is aliven");
pthread_join(tid, NULL);
return 0;
}
read is aliven");
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 77]# gcc -o pthread_kill_test pthread_kill_test.c -lpthread
[root@localhost 77]# ./pthread_kill_test
thread work:threadID=1648453376, count=50
thread work:threadID=1648453376, count=49
thread work:threadID=1648453376, count=48
thread work:threadID=1648453376, count=47
thread work:threadID=1648453376, count=46
main thread:threadID=1656735552
the specified thread is alive
thread work:threadID=1648453376, count=45
thread work:threadID=1648453376, count=44
thread work:threadID=1648453376, count=43
^C
根據(jù)上述運行結(jié)果可知,通過函數(shù)pthread_kill實現(xiàn)了子線程是否存活的檢測,即當(dāng)發(fā)送sig信號為0且子線程存活時,函數(shù)確實返回0。讓我們看一下當(dāng)子線程運行結(jié)束時,該函數(shù)會返回什么?修改上面pthread_kill_test.c源文件如下:
#include < stdio.h >
#include < pthread.h >
#include < signal.h >
#include < unistd.h >
#include < errno.h >
void *thrfunc(void *arg){
int count = 5;
while(count >0){
printf("thread work:threadID=%d, count=%dn",pthread_self(),count);
sleep(1);
count--;
}
return (void*)0;
}
int main(){
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, thrfunc, NULL);
sleep(10);
printf("main thread:threadID=%dn",pthread_self());
int kill_rc = pthread_kill(tid,0);
if(kill_rc == ESRCH)
printf("the specified thread did not exists or already quitn");
else if(kill_rc == EINVAL)
printf("signal is invalidn");
else
printf("the specified thread is aliven");
pthread_join(tid, NULL);
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 77]# vi pthread_kill_test.c
[root@localhost 77]# gcc -o pthread_kill_test pthread_kill_test.c -lpthread
[root@localhost 77]# ./pthread_kill_test
thread work:threadID=700397312, count=5
thread work:threadID=700397312, count=4
thread work:threadID=700397312, count=3
thread work:threadID=700397312, count=2
thread work:threadID=700397312, count=1
main thread:threadID=708679488
the specified thread did not exists or already quit
根據(jù)運行結(jié)果可知,當(dāng)子線程運行結(jié)束,函數(shù)確實返回了ESRCH。由此可見,根據(jù)pthread_kill函數(shù)的返回值,我們就可以判斷線程是否存活。
接下來讓我們再通過實例演示一下pthread_kill發(fā)送其他信號給線程,源碼如下:
#include < stdio.h >
#include < unistd.h >
#include < signal.h >
#include < errno.h >
#include < pthread.h >
#include < stdlib.h > // sleep() 函數(shù)
//signal處理函數(shù)
static void signal_handler(int sig_num)
{
if (sig_num == SIGUSR1) {
printf("thread quit by myown Custom Signal!n");
(void)pthread_exit((void *)1);
}
return;
}
//線程執(zhí)行的函數(shù)
void * thread_Fun(void * arg) {
if (signal(SIGUSR1, signal_handler) == SIG_ERR) {
printf("Fail to register signaln");
}
printf("子線程開始執(zhí)行n");
printf("子線程ID:%dn",pthread_self());
while(1);
}
int main()
{
pthread_t tid;
int value;
int res;
//創(chuàng)建tid 線程
res = pthread_create(&tid, NULL, thread_Fun, NULL);
if (res != 0) {
printf("線程創(chuàng)建失敗n");
return 0;
}
sleep(2);
//檢測tid線程是否存在
int kill_rc = pthread_kill(tid, 0);
if (kill_rc == 0) {
printf("Child thread exists and then kill it.n");
pthread_kill(tid, SIGUSR1);
}
printf("sleep.n");
pthread_join(tid, NULL);
sleep(3);
printf("main exit.n");
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 77]# gcc -o pthread_kill_quit_test pthread_kill_quit_test.c -lpthread
[root@localhost 77]# ./pthread_kill_quit_test
子線程開始執(zhí)行
子線程ID:607971072
Child thread exists and then kill it.
sleep.
thread quit by myown Custom Signal!
main exit.
這里通過pthread_kill發(fā)送用戶自定義信號SIGUSR1,如果不設(shè)置signal函數(shù),默認會使進程終止,這里通過設(shè)置signal處理函數(shù),自定義對線程的處理,即直接退出子線程。
7、線程 ID 比較
在 Linux 中線程 ID 本質(zhì)就是一個無符號長整形,因此可以直接使用比較操作符比較兩個線程的 ID,但是線程庫是可以跨平臺使用的,在某些平臺上 pthread_t 可能不是一個單純的整形,這種情況下比較兩個線程的 ID 必須要使用比較函數(shù),該函數(shù)主要用于檢查兩個線程是否相等,函數(shù)原型如下:
int pthread_equal(pthread_t t1, pthread_t t2);
參數(shù)說明:
- t1:要比較的線程的線程 ID
- t2:要比較的線程的線程 ID
返回值說明:
- 如果兩個線程 ID 相等返回非 0 值,如果不相等返回 0
引用頭文件 :#include
接下來讓我們通過實例演示一下pthread_equal比較兩個線程的ID是否相等,源碼如下:
#include < stdio.h >
#include < stdlib.h >
#include < unistd.h >
#include < sys/types.h >
#include < pthread.h >
void* func_one(void* ptr)
{
int cnt=3;
while(cnt--)
{
printf("thread 1ID:%dn",pthread_self());
}
return (void*)1;
}
void* func_two(void* ptr)
{
printf("thread 2 ID:%dn", pthread_self());
sleep(5);
return 0;
}
int main()
{
pthread_t thread_one, thread_two;
// 創(chuàng)建線程一
pthread_create(&thread_one, NULL, func_one, NULL);
// 創(chuàng)建線程二
pthread_create(&thread_two, NULL, func_two, NULL);
// 等待線程一運行結(jié)束
pthread_join(thread_one, NULL);
// 等待線程二運行結(jié)束
pthread_join(thread_two, NULL);
if(pthread_equal(thread_one,thread_two))
{
printf("thread 1 and thread 2 equaln");
}
else
{
printf("thread 1 and thread 2 not equaln");
}
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 77]# gcc -o pthread_equal_test pthread_equal_test.c -lpthread
[root@localhost 77]# ./pthread_equal_test
thread 1ID:1515988736
thread 1ID:1515988736
thread 1ID:1515988736
thread 2 ID:1507596032
thread 1 and thread 2 not equal
根據(jù)上述輸出結(jié)果可知,這兩個線程不相等。
四、顯示進程中的線程信息
在Linux中,程序中創(chuàng)建的線程(也稱為輕量級進程,LWP)會具有和程序的PID相同的“線程組ID”,該線程即為主線程。然后,各個線程會獲得其自身的線程ID(TID)。對于Linux內(nèi)核調(diào)度器而言,線程不過是恰好共享特定資源的標(biāo)準(zhǔn)的進程而已。經(jīng)典的命令行工具,如ps或top,都可以用來顯示線程級別的信息,只是默認情況下它們顯示進程級別的信息。這里提供了在Linux上顯示某個進程的線程的幾種方式。
1、ps命令
在ps命令中,“-T”或"-L"選項可以開啟線程查看。下面的命令列出了由進程號為的進程創(chuàng)建的所有線程。
方法一:
ps -T -p < pid >
選項說明:
- -T :顯示線程,帶有SPID列。
SPID:system process id,Linux下面表示線程。
方法二:
ps -L -p < pid >
選項說明:
- -L :顯示線程,可能有LWP和NLWP列。
LWP:線程ID
NLWP:線程組內(nèi)線程的個數(shù)。
自定義進程pthread_multithread_test,其中起2個線程,源碼如下:
#include < stdio.h >
#include < stdlib.h >
#include < unistd.h >
#include < sys/types.h >
#include < pthread.h >
void* func_one(void* ptr)
{
while(1)
{}
return (void*)1;
}
void* func_two(void* ptr)
{
while(1);
return 0;
}
int main()
{
pthread_t thread_one, thread_two;
// 創(chuàng)建線程一
pthread_create(&thread_one, NULL, func_one, NULL);
// 創(chuàng)建線程二
pthread_create(&thread_two, NULL, func_two, NULL);
// 等待線程一運行結(jié)束
pthread_join(thread_one, NULL);
// 等待線程二運行結(jié)束
pthread_join(thread_two, NULL);
return 0;
}
使用gcc編譯源文件,運行可執(zhí)行程序,如下:
[root@localhost 77]# ./pthread_multithread_test
[master@localhost 77]$ ps -ef | grep pthread_multithread_test
root 47359 3835 99 03:12 pts/0 00:00:38 ./pthread_multithread_test
master 47422 47370 0 03:12 pts/1 00:00:00 grep --color=auto pthread_multithread_test
[master@localhost 77]$ ps -T -p 47359
PID SPID TTY TIME CMD
47359 47359 pts/0 00:00:00 pthread_multith
47359 47360 pts/0 00:00:45 pthread_multith
47359 47361 pts/0 00:00:46 pthread_multith
“SPID”欄表示線程ID,其中SPID為47359的線程為主線程,而“CMD”欄則顯示了線程名稱。
我們再重新運行一下pthread_multithread_test進程,使用方法二查看如下:
[master@localhost 77]$ ps -ef | grep pthread_multithread_test
root 47777 3835 99 03:18 pts/0 00:00:22 ./pthread_multithread_test
master 47789 47370 0 03:19 pts/1 00:00:00 grep --color=auto pthread_multithread_test
[master@localhost 77]$ ps -L -p 47777
PID LWP TTY TIME CMD
47777 47777 pts/0 00:00:00 pthread_multith
47777 47778 pts/0 00:00:37 pthread_multith
47777 47779 pts/0 00:00:37 pthread_multith
"LWP"欄表示線程ID,LWP即Light Weight Process(輕量級進程),其中LWP為47777的線程為主線程,同樣“CMD”欄則顯示了線程名稱。
2、Top命令
top命令可以實時顯示各個線程情況。要在top輸出中開啟線程查看,請調(diào)用top命令的“-H”選項,該選項會列出所有Linux線程。在top運行時,你也可以通過按“H”鍵將線程查看模式切換為開或關(guān)。
要讓top輸出某個特定進程并檢查該進程內(nèi)運行的線程狀況,命令如下:
top -H -p < pid >
top -Hp $pid 可以查看進程pid下所有的線程列表,Shift+H (進程和線程切換顯示)
Shift+H切換至只顯示進程
Shift+H切換至顯示所有線程
3、Htop
一個對用戶更加友好的方式是,通過htop查看單個進程的線程,它是一個基于ncurses的交互進程查看器。該程序允許你在樹狀視圖中監(jiān)控單個獨立線程。
要在htop中啟用線程查看,請開啟htop,然后按來進入htop的設(shè)置菜單。選擇“設(shè)置”欄下面的“顯示選項”,然后開啟“樹狀視圖”和“顯示自定義線程名”選項。按退出設(shè)置。
現(xiàn)在,你就會看到下面這樣單個進程的線程視圖。
可以看到pthread_multithread_test進程以及其創(chuàng)建的2個線程。
4、/proc/pid/task/tid/status
Linux下面procfs文件系統(tǒng)下面的/proc/pid/status文件內(nèi)記錄進程的詳細信息,在使用時,其中pid替換成相應(yīng)進程的進程號。
[root@localhost 77]# cat /proc/51174/status
Name: pthread_multith
State: S (sleeping)
Tgid: 51174
Ngid: 0
Pid: 51174
PPid: 3835
TracerPid: 0
Uid: 0 0 0 0
Gid: 0 0 0 0
FDSize: 256
Groups: 0
VmPeak: 22840 kB
VmSize: 22840 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 376 kB
VmRSS: 376 kB
RssAnon: 80 kB
RssFile: 296 kB
RssShmem: 0 kB
VmData: 16588 kB
VmStk: 136 kB
VmExe: 4 kB
VmLib: 1972 kB
VmPTE: 36 kB
VmSwap: 0 kB
Threads: 3
SigQ: 0/7179
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: 0000000180000000
CapInh: 0000000000000000
CapPrm: 0000001fffffffff
CapEff: 0000001fffffffff
CapBnd: 0000001fffffffff
CapAmb: 0000000000000000
Seccomp: 0
Cpus_allowed: ffffffff,ffffffff,ffffffff,ffffffff
Cpus_allowed_list: 0-127
Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list: 0
voluntary_ctxt_switches: 2
nonvoluntary_ctxt_switches: 1
而/proc/pid/task/tid/status文件內(nèi)記錄進程中的線程的詳細信息,其中pid為進程號,tid為線程號,需要查看哪一個線程,該tid的值就切換成其相應(yīng)的值。/proc/pid/task/目錄下則保存著進程內(nèi)的線程,每個線程對應(yīng)task下的一個目錄。具體如下:
[root@localhost 77]# ps -ef | grep pthread
root 51174 3835 99 05:31 pts/0 00:00:26 ./pthread_multithread_test
root 51226 48895 0 05:31 pts/2 00:00:00 grep --color=auto pthread
//查看進程pthread_multithread_tes內(nèi)有幾個線程,task下的每個目錄表示一個線程
[root@localhost 77]# ls -l /proc/51174/task/
總用量 0
dr-xr-xr-x 6 root root 0 7月 9 05:31 51174
dr-xr-xr-x 6 root root 0 7月 9 05:31 51175
dr-xr-xr-x 6 root root 0 7月 9 05:31 51176
//查看線程51175的詳細信息
[root@localhost 77]# cat /proc/51174/task/51175/status
Name: pthread_multith
State: R (running)
Tgid: 51174
Ngid: 0
Pid: 51175
PPid: 3835
TracerPid: 0
Uid: 0 0 0 0
Gid: 0 0 0 0
FDSize: 256
Groups: 0
VmPeak: 22840 kB
VmSize: 22840 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 376 kB
VmRSS: 376 kB
RssAnon: 80 kB
RssFile: 296 kB
RssShmem: 0 kB
VmData: 16588 kB
VmStk: 136 kB
VmExe: 4 kB
VmLib: 1972 kB
VmPTE: 36 kB
VmSwap: 0 kB
Threads: 3
SigQ: 0/7179
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: 0000000180000000
CapInh: 0000000000000000
CapPrm: 0000001fffffffff
CapEff: 0000001fffffffff
CapBnd: 0000001fffffffff
CapAmb: 0000000000000000
Seccomp: 0
Cpus_allowed: ffffffff,ffffffff,ffffffff,ffffffff
Cpus_allowed_list: 0-127
Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list: 0
voluntary_ctxt_switches: 0
nonvoluntary_ctxt_switches: 36154
評論
查看更多