int pthread_join(pthread_t thread, void **retval);
作用:阻塞等待線程退出,獲取線程退出狀態。其作用對應進程中 waitpid() 函數。
成功:0;失敗:錯誤號 ??strerror函數
參數:thread:線程ID (注意:不是指針);retval:存儲線程結束狀態。
對比記憶:在wait回收子進程時,子進程exit或return返回的值即為status的值(int類型),wait函數第一個形參為回收進程的pid,第二個形參為&status(int *類型指針);對于回收子線程時,子線程退出的值(pthread_exit函數,或return返回的值)都為void *類型,則pthread_join函數的第一個形參為線程ID(pthread_t類型),第二個形參為void **類型。
調用該函數的線程將掛起等待,直到id為thread的線程終止。thread線程以不同的方法終止,通過pthread_join得到的終止狀態是不同的,總結如下:
- 如果thread線程通過return返回,retval所指向的單元里存放的是thread線程函數的返回值(即void *指針);
- 如果thread線程被別的線程調用pthread_cancel異常終止掉,retval所指向的單元里存放的是常數PTHREAD_CANCELED。
- 如果thread線程是自己調用pthread_exit終止的,retval所指向的單元存放的是傳給pthread_exit的參數(即void *指針),其實1與3是等效的。
- 如果對thread線程的終止狀態不感興趣,可以傳NULL給retval參數。
對應于僵尸進程,也是存在僵尸線程。因此,在線程結束時,也需要對線程進行回收以釋放僵尸線程所占用的資源。對進程的回收有區別的就是,一個進程中的所有線程都是可以互相回收的,即不是只能由主控線程回收子線程,兄弟線程之間也可以回收。一次pthread_join只能回收一個線程。回收線程時也可以不關心線程結束時的狀態,pthread_join函數的參數傳NULL即可,此時不關心線程結束時的狀態,只是將其回收并釋放線程所占用的資源。
//循環創建n個子線程,并回收這n個子線程
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>typedef struct {int a;char b;char str[10];
}exit_t; //返回值為一個結構體void *ftn( void *arg )
{int s;exit_t *retval; //retval的地址在棧空間,但是retval本身卻位與堆空間(heap),malloc所分配的。retval=(exit_t *)malloc( sizeof(exit_t) ); //必須用malloc分配,不能用線程棧空間s=(int)arg; //注意只能傳值,不能傳地址(用于判別是哪個線程)if( s<=1 ){retval->a=10;retval->b='a';strcpy(retval->str,"zsx");}if( s>1 && s<=3 ){retval->a=20;retval->b='b';strcpy(retval->str,"rgf");}if( s>3 ){retval->a=30;retval->b='c';strcpy(retval->str,"zy");}printf("I am %dth thread, and my ID is %lu.\n",s+1,pthread_self( ));pthread_exit((void *)retval); //或者 return (void *)retval; 兩者等價!
}int main(int argc, char *argv[ ])
{int n=5, i, ret;if( argc==2 )n=atoi(argv[1]);pthread_t tid[n];for(i=0;i<n;i++){ret=pthread_create(&tid[i],NULL,ftn,(void *)i); //注意只能傳值,不能傳地址,因為地址對應的i值會變化if( ret!=0){fprintf(stderr,"pthread_create error: %s\n",strerror(ret));exit(1);}}for(i=0;i<n;i++) //回收每個子線程{exit_t *re; //re位于主控線程的棧空間,但是re本身的值為子線程傳給它的值。ret=pthread_join( tid[i],(void **)&re);if( ret!=0){fprintf(stderr,"pthread_join error: %s\n",strerror(ret));exit(1);}printf("the %dth thread: a=%d, b=%c, str=%s.\n",i+1,re->a,re->b,re->str);free(re); //注意必須釋放malloc分配的空間,防止內存泄漏re = NULL; //置空,防止使用幽靈指針}printf("In main: the PID is %d, and the TID is %lu.\n",getpid( ),pthread_self( ));pthread_exit((void *)1);
}
[root@localhost 01_pthread_test]# ./ptrd_join
I am 4th thread, and my ID is 4124101440.
I am 2th thread, and my ID is 4140886848.
I am 1th thread, and my ID is 4149279552.
I am 5th thread, and my ID is 4115708736.
I am 3th thread, and my ID is 4132494144.
the 1th thread: a=10, b=a, str=zsx.
the 2th thread: a=10, b=a, str=zsx.
the 3th thread: a=20, b=b, str=rgf.
the 4th thread: a=20, b=b, str=rgf.
the 5th thread: a=30, b=c, str=zy.
In main: the PID is 10415, and the TID is 4151490816.
分析討論:
- 強調一點:子線程的返回值為void *指針,其指針所指向的地址必須位于全局區或者malloc分配的空間,不能位于用戶棧空間;
- 之所以子線程的返回值以及線程函數ftn的形參都要為void *類型,這是為了方便可以傳遞任何數據類型;
- 其實回收子線程的返回值的方式有很多種:a.第一種方式:上述程序中,exit_t *retval; 然后直接返回retval這個地址給主控線程,但是地址不能是用戶棧空間,因此用malloc函數分配空間后,再賦值返回;b.第二種方式:也可以exit_t retval,即直接返回retval本身這個值,即return (void *)retval; 主控線程中:exit_t *re; (exit_t)re即可,但是不建議這樣做,兼容性不高。這種方法類似于上面的i傳參方式;c.第三種方式:還是按值傳遞,即利用主控線程中的pthread_create函數中的最后一個參數,將值傳給子線程,然后讓子線程返回回來,即:主控線程: exit_t *re=(exit_t *)malloc( sizeof(exit_t) ), 然后(void *)re作為其最后一個參數傳遞給子線程的arg參數,則子線程中:(exit_t *)arg,然后給arg賦值,最后(void *)arg返回即可!
- 注意在線程函數中:pthread_exit((void *)retval);不能寫為:pthread_exit((void *)&retval);? 同樣的錯誤,即這樣相當于傳遞的是子線程用戶棧空間的地址。