Linux 進(jìn)程與線程
學(xué)習(xí)目標(biāo)
①了解線程的定義
②掌握常用的線程操作
③熟練通過線程屬性設(shè)置線程狀態(tài)
④掌握線程同步的方法
從很多Linux的書籍我們都可以這樣子描述進(jìn)程(process)和線程(thread)的: 進(jìn)程是資源管理的最小單位,線程是程序執(zhí)行的最小單位。
線程的本質(zhì)是一個(gè)進(jìn)程內(nèi)部的一個(gè)控制序列,它是進(jìn)程里面的東西,一個(gè)進(jìn)程可以擁有一個(gè)進(jìn)程或者多個(gè)進(jìn)程。
當(dāng)進(jìn)程執(zhí)行fork()函數(shù)創(chuàng)建一個(gè)進(jìn)程時(shí),將創(chuàng)建出該進(jìn)程的一份新副本。 這個(gè)新進(jìn)程擁有自己的變量和自己的PID,它的執(zhí)行幾乎完全獨(dú)立于父進(jìn)程, 這樣子得到一個(gè)新的進(jìn)程開銷是非常大的。而當(dāng)在進(jìn)程中創(chuàng)建一個(gè)新線程時(shí),新的執(zhí)行線程將擁有自己的棧, 但與它的創(chuàng)建者共享全局變量、文件描述符、信號(hào)處理函數(shù)和當(dāng)前目錄狀態(tài)。 也就是說,它只使用當(dāng)前進(jìn)程的資源,而不是產(chǎn)生當(dāng)前進(jìn)程的副本。
與進(jìn)程不同,線程(thread)是系統(tǒng)調(diào)度分配的最小單位。與進(jìn)程相比,線程沒有獨(dú)立的地址空間,多個(gè)線程共享一段地址空間,因此線程消耗更少的內(nèi)存資源,線程間通信也更為方便,有時(shí)線程也被稱為輕量級(jí)進(jìn)程(Light Weight Process,LWP)。本章將會(huì)介紹與線程相關(guān)的知識(shí),包括線程的概念、線程的生命周期以及與線程相關(guān)的系統(tǒng)調(diào)用(如創(chuàng)建線程、銷毀線程、線程同步)等。
1.線程概述
早期操作系統(tǒng)沒有線程這一概念,無論是分配資源還是調(diào)度分配,都以進(jìn)程為最小的單元。
操作系統(tǒng)應(yīng)調(diào)度一個(gè)更小的單位,以減少消耗,提高效率,由此線程應(yīng)運(yùn)而生。
Linux系統(tǒng)中的線程借助進(jìn)程機(jī)制實(shí)現(xiàn),線程與進(jìn)程聯(lián)系密切。進(jìn)程可以蛻變成線程,當(dāng)一個(gè)進(jìn)程中創(chuàng)建一個(gè)線程時(shí),原有的進(jìn)程就會(huì)變成線程,兩個(gè)線程共用一段地址空間;線程有被稱為輕量級(jí)進(jìn)程,線程的TCB(Thread Contorl Block,線程控制塊)與進(jìn)程的PCB相同,因此也可以將TCB視為PCB;對(duì)內(nèi)核而言,線程與進(jìn)程沒有區(qū)別,CPU會(huì)為每個(gè)線程與進(jìn)程分配時(shí)間片,并通過PCB來調(diào)度不同的進(jìn)程和線程。
Linux系統(tǒng)中的線程分為三種:內(nèi)核線程、用戶線程和輕量級(jí)線程(TWP)。
①內(nèi)核線程是內(nèi)核的分支,每個(gè)內(nèi)核線程可處理一項(xiàng)特定操作。
②用戶線程是完全建立在用戶空間的線程,用戶線程的創(chuàng)建、調(diào)度、銷毀等操作都在用戶空間完成,是一種低消耗、高效率的線程。
③輕量級(jí)線程是一種用戶線程,同時(shí)也是內(nèi)核線程的高級(jí)抽象,每一個(gè)輕量級(jí)線程都需要一個(gè)內(nèi)核線程支持,輕量級(jí)線程與內(nèi)核及CPU之間的關(guān)系。
·
一個(gè)進(jìn)程的實(shí)體可以分為兩大部分:線程集和資源集。
①線程集是多個(gè)線程的集合,每個(gè)線程都是進(jìn)程中的動(dòng)態(tài)對(duì)象 。
②資源集是進(jìn)程中線程集共享資源的集合,包括地址空間、打開的文件描述符、用戶信息等。
一個(gè)線程的實(shí)體包括程序、數(shù)據(jù)、TCB以及少量必不可少的用于保證線程獨(dú)立運(yùn)行的資源。
使用多線程編程時(shí),程序的并發(fā)性會(huì)得到一定的提升。若一個(gè)進(jìn)程細(xì)分為多個(gè)線程,那么一個(gè)進(jìn)程中的多個(gè)線程可以同時(shí)在不同的CPU上運(yùn)行,如此可在一定程度上減少程序的運(yùn)行時(shí)間,提高程序的執(zhí)行效率。
Linux系統(tǒng)中的每個(gè)進(jìn)程都有獨(dú)立的地址空間,一個(gè)進(jìn)程崩潰后, 在系統(tǒng)的保護(hù)模式下并不會(huì)對(duì)系統(tǒng)中其它進(jìn)程產(chǎn)生影響,而線程只是一個(gè)進(jìn)程內(nèi)部的一個(gè)控制序列, 當(dāng)進(jìn)程崩潰后,線程也隨之崩潰,所以一個(gè)多進(jìn)程的程序要比多線程的程序健壯,但在進(jìn)程切換時(shí), 耗費(fèi)資源較大,效率要差一些。但在某些場(chǎng)合下對(duì)于一些要求同時(shí)進(jìn)行并且又要共享某些變量的并發(fā)操作, 只能用線程,不能用進(jìn)程。
總的來說:
一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程。
線程使用的資源是進(jìn)程的資源,進(jìn)程崩潰線程也隨之崩潰。
線程的上下文切換,要比進(jìn)程更加快速,因?yàn)楸举|(zhì)上,線程很多資源都是共享進(jìn)程的,所以切換時(shí), 需要保存和切換的項(xiàng)是很少的。
2. 創(chuàng)建線程
在講解線程編程之前,先了解一個(gè)標(biāo)準(zhǔn):可移植操作系統(tǒng)接口 (Portable Operating System Interface,縮寫為POSIX), POSIX是IEEE為要在各種UNIX操作系統(tǒng)上運(yùn)行軟件,而定義API接口的一系列互相關(guān)聯(lián)的標(biāo)準(zhǔn)的總稱, 其正式稱呼為IEEEStd 1003,而國(guó)際標(biāo)準(zhǔn)名稱為ISO/IEC9945,此標(biāo)準(zhǔn)源于一個(gè)大約開始于1985年的項(xiàng)目。 POSIX這個(gè)名稱是由理查德·斯托曼(RMS)應(yīng)IEEE的要求而提議的一個(gè)易于記憶的名稱。 它基本上是Portable Operating System Interface(可移植操作系統(tǒng)接口)的縮寫, 而X則表明其對(duì)Unix API的傳承。
簡(jiǎn)單來說,如果應(yīng)用程序使用POSIX標(biāo)準(zhǔn)的接口來調(diào)用系統(tǒng)函數(shù), 那么應(yīng)用程序?qū)⒎浅H菀滓浦采踔林苯蛹嫒莸阶裱璓OSIX標(biāo)準(zhǔn)的系統(tǒng)上。
在Linux系統(tǒng)下的多線程遵循POSIX標(biāo)準(zhǔn),而其中的一套常用的線程庫(kù)是 pthread, 它是一套通用的線程庫(kù),是由 POSIX提出的,因此具有很好的可移植性, 我們學(xué)習(xí)的Linux多線程編程也正是使用它,使用時(shí)必須包含以下頭文件:
#include <pthread.h>
除此之外在鏈接時(shí)需要使用庫(kù)libpthread.a。因?yàn)閜thread的庫(kù)不是Linux系統(tǒng)的庫(kù), 所以在編譯時(shí)要加上-lpthread 選項(xiàng)。
gcc pthread_cre.c -o pthread_cre -lpthread
Linux系統(tǒng)中創(chuàng)建線程的系統(tǒng)調(diào)用接口為pthread_create(),該函數(shù)存在于函數(shù)庫(kù)pthread.h,其聲明如下:
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,
void *(*start_routine)(void *),void *arg);
如果調(diào)用pthread_create()函數(shù)創(chuàng)建線程成功,會(huì)返回0;
若線程創(chuàng)建失敗,則直接返回errno。
此外,由于errno的值很容易被修改,線程中很少使用errno來存儲(chǔ)錯(cuò)誤碼,也不會(huì)使用perror()直接將其打印,而是1.使用自定義變量接收errno,2.再調(diào)用strerror()將獲取的錯(cuò)誤碼轉(zhuǎn)換成錯(cuò)誤信息,3.最后才打印錯(cuò)誤信息。
pthread_create()函數(shù)中包含4個(gè)參數(shù):
①參數(shù)thread表示待創(chuàng)建線程的線程id指針,這是一個(gè)傳入傳出參數(shù),若需要對(duì)該線程進(jìn)行操作,應(yīng)使用一個(gè)pthread_t *類型的變量獲取該參數(shù);(指向線程標(biāo)識(shí)符的指針)
②參數(shù)arr用于設(shè)置待創(chuàng)建線程的屬性,通常傳入NULL,表示使用線程的默認(rèn)屬性;(設(shè)置線程屬性)
?
③參數(shù)start_routine是一個(gè)函數(shù)指針,指向一個(gè)參數(shù)為void*、返回值也為void*的函數(shù),該函數(shù)為待創(chuàng)建線程執(zhí)行函數(shù),線程創(chuàng)建成功后將執(zhí)行該函數(shù)中的代碼;(start_routine是一個(gè)函數(shù)指針,指向要運(yùn)行的線程入口,即線程運(yùn)行時(shí)要執(zhí)行的函數(shù)代碼。)
④參數(shù)arg為要傳入給線程執(zhí)行函數(shù)的參數(shù);(運(yùn)行線程時(shí)傳入的參數(shù))
返回值:若線程創(chuàng)建成功,則返回0。
若線程創(chuàng)建失敗,則返回對(duì)應(yīng)的錯(cuò)誤代碼。
在線程調(diào)用pthread_create()函數(shù)創(chuàng)建出新線程之后,當(dāng)前線程會(huì)從pthread_create()函數(shù)返回并繼續(xù)向下執(zhí)行,新線程會(huì)執(zhí)行函數(shù)指針start_routine所指的函數(shù)。
若pthread_create()函數(shù)成功返回,新線程的id會(huì)被寫到thread參數(shù)所指向的內(nèi)存單元。
需要注意的是,進(jìn)程id的類型pid_t實(shí)質(zhì)是一個(gè)正整數(shù),在整個(gè)系統(tǒng)中都是唯一的;但線程id只在當(dāng)前進(jìn)程中保證唯一,其類型pthread_t并非是一個(gè)正整數(shù),且當(dāng)前進(jìn)程調(diào)用pthread_create()后獲取的thread為新線程id。
?
因此線程id不能簡(jiǎn)單地使用printf()函數(shù)打印,而應(yīng)使用Linux提供的接口函數(shù)pthread_self()來獲取。
pthread_self()函數(shù)存在于函數(shù)庫(kù)pthread.h中,其聲明如下:
pthread_t pthread_self(void);
下面通過一個(gè)案例來展示pthread_create()函數(shù)的用法。
fprintf是C/C++中的一個(gè)格式化庫(kù)函數(shù),其作用是格式化輸出到一個(gè)流/文件中;函數(shù)原型為int fprintf( FILE *stream, const char *format, [ argument ]...),fprintf()函數(shù)根據(jù)指定的格式(format)向輸出流(stream)寫入數(shù)據(jù)(argument)。
案例9-1:使用pthread_create()函數(shù)創(chuàng)建線程,并使原線程與新線程分別打印自己的線程id。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *tfn(void *arg)
{
printf("tfn--pid=%d,tid=%lu\n",getpid(),pthread_self());
return (void *)0;
}
int main()
{
pthread_t tid;
printf("main--pid=%d,tid=%lu\n",getpid(),pthread_self());
int ret=pthread_create(&tid,NULL,tfn,NULL);
if(ret!=0){
fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
exit(1);
}
sleep(1);
return 0;
}
執(zhí)行結(jié)果進(jìn)程2883中的兩個(gè)線程分別打印出了各自的線程id,由此可知案例9-1實(shí)現(xiàn)成功
?
進(jìn)程擁有獨(dú)立的地址空間。當(dāng)使用fork()函數(shù)創(chuàng)建出新進(jìn)程后,若其中·一個(gè)進(jìn)程要對(duì)fork()之前的數(shù)據(jù)進(jìn)行修改,進(jìn)程中會(huì)依據(jù)“寫時(shí)復(fù)制”原則,先復(fù)制一份該數(shù)據(jù)到子進(jìn)程的地址空間,再修改數(shù)據(jù)。因此即便是全局變量,在進(jìn)程間也是不共享的。但由于線程間共享地址空間,因此在一個(gè)線程中對(duì)全局區(qū)的數(shù)據(jù)進(jìn)行修改,其他線程中訪問到的也是修改后的數(shù)據(jù)。
案例9-2:創(chuàng)建新線程,在新線程中修改原線程中定義在全局區(qū)的變量,并在原線程中打印該數(shù)據(jù)。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
int var=100;
void *tfn(void *arg)
{
var=200;
printf("thread\n");
return NULL;
}
int main(void)
{
printf("At first var=%d\n",var);
pthread_t tid;
pthread_create(&tid,NULL,tfn,NULL);
sleep(1);
printf("after pthread_create,var=%d\n",var);
return 0;
}
以上程序的原線程中定義了一個(gè)全局變量var,并賦值為100;隨后在新線程中修改中全局變量var的值并使原線程沉睡1秒,等待原線程執(zhí)行,確保原線程的打印語(yǔ)句在新進(jìn)程功能完成最后執(zhí)行;最后執(zhí)行;最后在原線程中打印var的值。編譯案例9-2,執(zhí)行程序,終端打印結(jié)果如下
?
原線程中訪問到的變量var的值被修改為200,說明新線程成功修改了原線程中定義的全局變量,線程之間共享全局?jǐn)?shù)據(jù)。
2.2 線程退出
線程中提供了一個(gè)用于單個(gè)線程退出的函數(shù)——pthread_exit(),該函數(shù)位于函數(shù)庫(kù)pthread.h中,其聲明如下:
void pthread_exit(void *retval);
在之前的案例中使用exit()和return雖然也有退出功能,但return用于退出函數(shù),使函數(shù)返回函數(shù)調(diào)用處;exit()用于退出進(jìn)程,若在線程中調(diào)用函數(shù),那么線程所處的進(jìn)程也會(huì)退出,如此勢(shì)必會(huì)影響進(jìn)程中線程的執(zhí)行。為避免這個(gè)問題,保證程序中的線程能逐個(gè)退出,Linux系統(tǒng)中又提供了pthread_exit()函數(shù)的用法。
pthread_exit()函數(shù)沒有返回值,其參數(shù)retval表示線程的退出狀態(tài),通常設(shè)置為NULL。
(
參數(shù)說明:
retval:如果retval不為空,則會(huì)將線程的退出值保存到retval中,如果不關(guān)心線程的退出值,形參為NULL即可。
)
下面通過一個(gè)案例來展示pthread_exit()函數(shù)的用法。
案例9-3:在一個(gè)進(jìn)程中創(chuàng)建4個(gè)新線程,分別使用pthread_exit()函數(shù)、return、exit()使其中一個(gè)線程退出,觀察其他線程的執(zhí)行狀況。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <unistd.h>
void *tfn(void *arg)
{
long int i;
i=(long int)arg; //將void*類型的arg強(qiáng)轉(zhuǎn)為long int類型
if(i==2)
pthread_exit(NULL);
sleep(i); //通過i來區(qū)別每個(gè)線程
printf("I'm %dth thread,Thread_ID=%lu\n",i+1,pthread_self());
return NULL;
}
int main(int argc,char *argv[])
{
long int n=5,i;
pthread_t tid;
if(argc==2)
n=atoi(argv[1]);
for(i=0;i<n;i++){
//將i轉(zhuǎn)換為指針,在tfn中強(qiáng)制轉(zhuǎn)換回整型
pthread_create(&tid,NULL,tfn,(void *)i);
}
sleep(n);
printf("I am main,I'm a thread!\n"
"main_thread_ID=%lu\n",pthread_self());
return 0;
}
執(zhí)行結(jié)果如下:
?
有執(zhí)行結(jié)果可知,使用pthread_exit()函數(shù)時(shí),只有調(diào)用該函數(shù)的線程會(huì)退出。
2.3 線程終止
在線程操作中有一個(gè)與終止進(jìn)程的函數(shù)kill()對(duì)應(yīng)的系統(tǒng)調(diào)用,即pthread_cancel(),使用該函數(shù)可以通過向指定線程發(fā)送CANCEL信號(hào),使一個(gè)線程強(qiáng)行殺死另外一個(gè)線程。pthread_cancel()函數(shù)位于函數(shù)庫(kù)pthread.h中,其聲明如下:
int pthread_cancel(pthread_t thread);
pthread_cancel()中的參數(shù)thread為線程id,若函數(shù)調(diào)用成功則返回0,否則返回errno。使用pthread_cancel()函數(shù)終止的線程其退出碼為PTHREAD_CANCELED,該宏定義在頭文件pthread.h中,其值為-1。
與進(jìn)程不同的是,調(diào)用pthread_cancel()函數(shù)殺死線程時(shí),需要等待線程到達(dá)某個(gè)取消點(diǎn),線程才會(huì)成功被終止。類似于單機(jī)游戲中只有到達(dá)城鎮(zhèn)中的存檔點(diǎn)時(shí)才能執(zhí)行存檔操作,在多線程編程中,只有到達(dá)取消點(diǎn)時(shí)系統(tǒng)才會(huì)檢測(cè)是否有未響應(yīng)的取消信號(hào),并對(duì)信號(hào)進(jìn)行處理。
所謂取消點(diǎn)即在線程執(zhí)行過程中會(huì)檢測(cè)是否有未響應(yīng)取消信號(hào)的點(diǎn),可粗略地認(rèn)為只要有系統(tǒng)調(diào)用(進(jìn)入內(nèi)核)發(fā)生,就會(huì)進(jìn)入取消點(diǎn),如在程序中調(diào)用read()、write()、pause()等函數(shù)時(shí)都會(huì)出現(xiàn)取消點(diǎn)。取消點(diǎn)通常伴隨阻塞出現(xiàn),用戶也可以在程序中通過調(diào)用pthread_testcancel()函數(shù)創(chuàng)造取消點(diǎn)。
下面通過一個(gè)案例來展示pthread_cancel()函數(shù)的用法。
案例9-4:在程序中使用pthread_cancel()函數(shù)使原線程終止指定線程。
進(jìn)程中的線程可以調(diào)用pthread_join()函數(shù)來等待某個(gè)線程的終止,獲得該線程的終止?fàn)顟B(tài)(),并收回所占的資源, 如果對(duì)線程的返回狀態(tài)不感興趣,可以將rval_ptr設(shè)置為NULL。
int pthread_join(pthread_t tid, void **rval_ptr);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *tfn(void *arg)
{
while(1){
printf("child thread...\n");
pthread_testcancel(); //設(shè)置取消點(diǎn)
}
}
int main(void)
{
pthread_t tid;
void *tret=NULL;
pthread_create(&tid,NULL,tfn,NULL);
sleep(1);
pthread_cancel(tid);
pthread_join(tid,&tret);
printf("child thread exit code=%ld\n",(long int)tret);
return 0;
}
執(zhí)行結(jié)果如下:
?
由執(zhí)行結(jié)果可知,新線程在1秒后被原線程終止,新線程的退出碼為-1,即PTHREAD_CANCELED。
pthread_exit()和pthread_cancel()都是線程機(jī)制中提供的用于終止線程的系統(tǒng)調(diào)用,pthread_exit()使線程主動(dòng)退出,pthread_cancel()通過信號(hào)使線程被動(dòng)退出。需要注意的是,由于在線程機(jī)制出現(xiàn)之前信號(hào)機(jī)制已經(jīng)出現(xiàn),信號(hào)機(jī)制在創(chuàng)建時(shí)并未將線程考慮在內(nèi),線程與信號(hào)機(jī)制的兼容性略有不足,因此在多線程編程中應(yīng)盡量避免使用信號(hào),以免出現(xiàn)難以調(diào)試的錯(cuò)誤。
2.4 線程掛起
在進(jìn)程中,可以使用wait()、waitpid()將進(jìn)程掛起,以等待某個(gè)子進(jìn)程結(jié)束;而在線程中,則通過pthread_join()函數(shù)掛起線程。pthread_join()函數(shù)存在于函數(shù)庫(kù)pthread.h中,其函數(shù)聲明如下:
int pthread_join(pthread_t thread,void **retval);
調(diào)用該函數(shù)的線程將會(huì)使自己掛起并等待指定線程thread結(jié)束。需要注意的是,該函數(shù)中指定的線程必須與調(diào)用該函數(shù)的線程處于同一進(jìn)程中,且多個(gè)線程不能同時(shí)掛起等待同一個(gè)進(jìn)程,否則pthread_join()將會(huì)返回錯(cuò)誤。
?
pthread_join()調(diào)用成功將返回0,否則返回errno。
pthread_join()中的參數(shù)thread表示被等待的線程id;
參數(shù)retval用于接收thread線程執(zhí)行函數(shù)的返回值指針,該指針的值與thread線程的終止方式有關(guān):
①若thread線程通過return返回,retval所指的存儲(chǔ)單元中存放的是thread線程函數(shù)的返回值。
②若thread線程被其他線程通過系統(tǒng)調(diào)用pthread_cancel()異常終止,retval所指向的存儲(chǔ)單元中存放的是常量PTHREAD_CANCELED。
③若thread線程通過自調(diào)用pthread_exit()終止,retval所指向的存儲(chǔ)單元中存放的是pthread_exit()中的參數(shù)ret_val。
④若等待thread的線程不關(guān)心它的終止?fàn)顟B(tài),可以將retval的值設(shè)置為NULL。
在使用線程時(shí),一般都使用pthread_exit()函數(shù)將其終止。下面通過一個(gè)簡(jiǎn)單案例來展示含參pthread_exit()函數(shù)的用法。
案例9-5:使用pthread_exit()退出線程,為線程設(shè)置退出狀態(tài)并將線程的退出狀態(tài)輸出。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
typedef struct{
int a;
int b;
}exit_t;
void *tfn(void *arg)
{
exit_t *ret;
ret=malloc(sizeof(exit_t));
ret->a=100;
ret->b=300;
pthread_exit((void *)ret); //線程終止
return NULL; //線程返回
}
int main(void)
{
pthread_t tid;
exit_t *retval;
pthread_create(&tid,NULL,tfn,NULL);
//調(diào)用pthread_join可以獲取線程的退出狀態(tài)
pthread_join(tid,(void **)&retval);
printf("a=%d,b=%d\n",retval->a,retval->b);
return 0;
}
在案例9-5創(chuàng)建的新線程中,即調(diào)用了thread_exit()函數(shù),又設(shè)置了關(guān)鍵字return;在程序的第24行中,使用pthread_join()等待新線程退出并獲取線程的退出狀態(tài),若第2中5行代碼打印的線程退出狀態(tài)不為空,說明線程通過pthread_exit()函數(shù)退出。
?
由執(zhí)行結(jié)果可知,第15行調(diào)用的pthread_exit()函數(shù)成功使線程退出,并設(shè)置了線程的退出狀態(tài)。
進(jìn)程中可以使用waitpid()函數(shù)結(jié)合循環(huán)結(jié)構(gòu)使原進(jìn)程等待多個(gè)進(jìn)程退出,線程中的pthread_join()同樣可以與循環(huán)結(jié)構(gòu)結(jié)合,等待多個(gè)線程退出。
案例9-6:使用pthread_join()回收多個(gè)新線程,并使用pthread_exit()獲取每個(gè)線程的退出狀態(tài)。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
long int var=100;
void *tfn(void *arg)
{
long int i;
i=(long int)arg;
sleep(i);
if(i==1){
var=333;
printf("var=%d\n",var);
pthread_exit((void *)var);
}
else if(i==3){
var=777;
printf("I'm %dth pthread,pthread_id=%lu\n"
"var=%d\n",i+1,pthread_self(),var);
pthread_exit((void *)var);
}
else{
printf("I'm %dth pthread,pthread_id=%lu\n"
"var=%d\n",i+1,pthread_self(),var);
pthread_exit((void *)var);
}
return NULL;
}
int main(void)
{
pthread_t tid[5];
long int i;
int *ret[5];
for(i=0;i<5;i++) //創(chuàng)建新線程
pthread_create(&tid[i],NULL,tfn,(void *)i);
for(i=0;i<5;i++){ //回收新線程
pthread_join(tid[i],(void **)&ret[i]);
printf("------------%d's ret=%d\n",i,(long int)ret[i]);
}
printf("I'm main pthread tid=%lu\t var=%d\n",pthread_self(),var);
pthread_exit(NULL);
}
執(zhí)行程序
?
由執(zhí)行結(jié)果可知,程序中創(chuàng)建的5個(gè)新線程都成功退出,案例9-6成功運(yùn)行。
當(dāng)然,原線程的退出之所以會(huì)導(dǎo)致其他線程退出,是因?yàn)樵€程執(zhí)行完畢后,main()函數(shù)中會(huì)隱式使用exit函數(shù),而我們知道pthread_exit()函數(shù)可以只使調(diào)用該函數(shù)的線程退出。
2.5 線程分離
在線程終止后, 其他線程會(huì)調(diào)用pthread_join()函數(shù)獲取該線程的終止信息,在此之前,線程會(huì)一直保持終止?fàn)顟B(tài),這種狀態(tài)類似進(jìn)程中的僵尸進(jìn)程。雖然處于僵尸態(tài)的進(jìn)程中大部分資源都已經(jīng)被釋放,但因?yàn)槿杂猩僭S資源殘留,進(jìn)程會(huì)保持僵尸態(tài)一直于系統(tǒng)detach()函數(shù),對(duì)進(jìn)程的這一不足做了完善。
pthread_detach()函數(shù)將會(huì)線程從主控線程中分離,這樣當(dāng)線程結(jié)束后,它的退出狀態(tài)不由其他線程獲取,而是由該線程自身自動(dòng)釋放。pthread_detach()函數(shù)位于函數(shù)庫(kù)pthread.h中,其聲明如下:
int pthread_detach(pthread_t thread);
參數(shù)thread為待分離線程的id,若該函數(shù)調(diào)用成功則返回0,否則返回errno。
需要注意的是,pthread_join()不能終止已處于detach狀態(tài)的線程,若對(duì)處于分離態(tài)的線程調(diào)用pthread_join()函數(shù),函數(shù)將會(huì)調(diào)用失敗并返回EINVAL。
下面通過一個(gè)案例來展示pthread_detach()函數(shù)的用法。
案例9-7:使用pthread_detach()函數(shù)分離新線程,使新線程自動(dòng)回收。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
void *tfn(void *arg)
{
int n=5;
while(n--)
{
printf("pthread tfn n=%d\n",n);
sleep(1);
}
return (void *)7;
}
int main(void)
{
pthread_t tid;
void *ret;
pthread_create(&tid,NULL,tfn,NULL);
pthread_detach(tid);
int retvar=pthread_join(tid,(void **)&ret);
if(retvar!=0)
{
fprintf(stderr,"pthread_join error %s\n",strerror(retvar));
}
else
{
printf("pthread exit with %ld\n",(long int)ret);
}
return 0;
}
執(zhí)行結(jié)果如下:
pthread exit with 1407334567
結(jié)合程序解析結(jié)果,對(duì)程序進(jìn)行分析。第6~14行代碼定義了tfn()函數(shù)。在第19行代碼中,tfn()函數(shù)作為新線程的執(zhí)行函數(shù)被傳遞給pthread_create()函數(shù)。第20行代碼調(diào)用pthread_detach()函數(shù)將第19行代碼創(chuàng)建的新線程從當(dāng)前線程中分離。第21行代碼調(diào)用pthread_join()函數(shù)將新線程掛起,新線程終止后,pthread_join()函數(shù)中的參數(shù)ret將獲取線程的終止?fàn)顟B(tài)。因?yàn)槌绦蛑袨樾戮€程的執(zhí)行函數(shù)tfn()設(shè)置了返回值“(void*)7”,所以若函數(shù)pthread_join()函數(shù)調(diào)用成功,pthread_join()函數(shù)的參數(shù)ret等于tfn()函數(shù)的返回值。
鏈接:https://www.dianjilingqu.com/484705.html