Linux 多線程之Joinable和Detached

一般情況下,進程中各個線程的運行都是相互獨立的,線程的終止並不會通知,也不會影響其他線程,終止的線程所佔用的資源也並不會隨着線程的終止而得到釋放。正如進程之間可以用wait()系統調用來同步終止並釋放資源一樣,線程之間也有類似機制,那就是pthread_join()函數。

 

void pthread_exit(void *retval) 
int pthread_join(pthread_t th, void **thread_return)
int pthread_detach(pthread_t th)

 

pthread_join()的調用者將掛起並等待th線程終止,retval是pthread_exit()調用者線程(線程ID爲th)的返回值,如果thread_return不爲NULL,則*thread_return=retval。需要注意的是一個線程僅允許唯一的一個線程使用pthread_join()等待它的終止,並且被等待的線程應該處於可join狀態,即非DETACHED狀態。

如果進程中的某個線程執行了pthread_detach(th),則th線程將處於DETACHED狀態,這使得th線程在結束運行時自行釋放所佔用的內存資源,同時也無法由pthread_join()同步,pthread_detach()執行之後,對th請求pthread_join()將返回錯誤。

一個可join的線程所佔用的內存僅當有線程對其執行了pthread_join()後纔會釋放,因此爲了避免內存泄漏,所有線程的終止,要麼已設爲DETACHED,要麼就需要使用pthread_join()來回收。

 

 

 首先看一下兩個函數的定義:

int pthread_join(
             pthread_t tid ,
             void **status
             );
// 參數tid 是希望等待的線程的線程號,status 是指向線程返回值的指針,線程的返回值就是pthread_exit 中的value_ptr 參數,或者是return語句中的返回值。該函數可用於線程間的同步


int pthread_detach( pthread_t pid );

//參數tid 是希望等待的線程的線程號, 把指定的線程轉變爲脫離狀態
一個線程或者是可匯合的(joinable,缺省值),或者是脫離的(detached)。當一個可匯合的線程終止時,它的線程ID和退出狀態將留到另一個線程對它調用pthread_join。脫離線程卻象守護進程:當它們終止的時,所有相關資源都被釋放,我們不能等待它們終止。如果一個線程需要知道另一個線程什麼時候終止,那就最好好吃第二個線程的可匯合狀態。

    下面 通過例子說明:

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

#define THREAD_NUMBER 2
int retval_hello1= 1, retval_hello2 = 2;

void* hello1(void *arg) 
{    
   char *hello_str = (char *)arg;
   sleep(2);
   printf("%s/n", hello_str);
   pthread_exit(&retval_hello1);
}

void* hello2(void *arg)
{
   char *hello_str = (char *)arg;
   sleep(1);
   printf("%s/n", hello_str);
   pthread_exit(&retval_hello2);
}

int main(int argc, char *argv[])
{
   int i;
   int ret_val;
   int *retval_hello[2];
   
   pthread_t pt[THREAD_NUMBER];
   const char *arg[THREAD_NUMBER];
   arg[0] = "hello world from thread1";
   arg[1] = "hello world from thread2";
   
   printf("Begin to create threads.../n");
   ret_val = pthread_create(&pt[0], NULL, hello1, (void *)arg[0]);
   if (ret_val != 0 ) {
      printf("pthread_create error!/n");
      exit(1);
   }

   ret_val = pthread_create(&pt[1], NULL, hello2, (void *)arg[1]);
   if (ret_val != 0 ) {
      printf("pthread_create error!/n");
      exit(1);
   }
   
   printf("Begin to wait for threads.../n");   
   for(i = 0; i < THREAD_NUMBER; i++) {
       ret_val = pthread_join(pt[i], (void **)&retval_hello[i]);
       if (ret_val != 0) {
          printf("pthread_join error!/n");
      exit(1);
       } else {
          printf("return value is %d/n", *retval_hello[i]);
       }
   }
   printf("Now, the main thread returns./n");
   return 0;
}

執行結果爲:

Begin to create threads...
Begin to wait for threads...
hello world from thread2
hello world from thread1
return value is 1
return value is 2
Now, the main thread returns.

線程1,2的執行順序可以通過sleep來調節,但是主線程必須在子線程完成之後才能執行,即打印”Now, the main thread returns.“。此外,因爲調用pthread_join()的順序,必定是線程1先執行“return value is xx”,不管線程2是否先執行完。

下面修改pthread_join爲pthread_detach(),代碼爲

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

#define THREAD_NUMBER 2
int retval_hello1= 1, retval_hello2 = 2;

void* hello1(void *arg) 
{    
   char *hello_str = (char *)arg;
   sleep(2);
   printf("%s/n", hello_str);
   pthread_exit(&retval_hello1);
}

void* hello2(void *arg)
{
   char *hello_str = (char *)arg;
   sleep(1);
   printf("%s/n", hello_str);
   pthread_exit(&retval_hello2);
}

int main(int argc, char *argv[])
{
   int i;
   int ret_val;
   int *retval_hello[2];
   
   pthread_t pt[THREAD_NUMBER];
   const char *arg[THREAD_NUMBER];
   arg[0] = "hello world from thread1";
   arg[1] = "hello world from thread2";
   
   printf("Begin to create threads.../n");
   ret_val = pthread_create(&pt[0], NULL, hello1, (void *)arg[0]);
   if (ret_val != 0 ) {
      printf("pthread_create error!/n");
      exit(1);
   }

   ret_val = pthread_create(&pt[1], NULL, hello2, (void *)arg[1]);
   if (ret_val != 0 ) {
      printf("pthread_create error!/n");
      exit(1);
   }
   
   printf("Begin to wait for threads.../n");   
   for(i = 0; i < THREAD_NUMBER; i++) {
       ret_val = pthread_detach(pt[i]);
       if (ret_val != 0) {
          printf("pthread_join error!/n");
      exit(1);
       }
   }
   printf("Now, the main thread returns./n");
   return 0;
}

執行結果爲

Begin to create threads...
Begin to wait for threads...
Now, the main thread returns. 
線程1,2沒有執行(也可能執行),因爲子線程爲可分離的,主線程在執行完之後即將進程銷燬,資源收回,導致子線程未運行。可以在return 0 語句之前加入sleep(5),這樣執行結果爲

Begin to create threads...
Begin to wait for threads...
Now, the main thread returns.
hello world from thread2
hello world from thread1 
所以,pthread_join()會掛起父線程,直至子線程完成纔可以執行後面的代碼,此外,一個PTHREAD_CREATE_JOINABLE狀態的子線程不會自動釋放該線程的內存資源,包括線程描述符和其使用的棧;而主線程調用pthread_detach()時,無需等待子線程的完成,它可以立即執行後面的代碼,當然,也有可能主線程執行完之後銷燬進程,導致子線程未能執行,此外,一個PTHREAD_CREATE_DETACH狀態的子線程擁有自我回收內存資源的功能。

 

二、線程處於兩種狀態下,退出時(也就是調用pthread_exit()函數)。對資源的釋放情況。

/******************************************

通過查看線程號,判斷。線程退出時,是否釋放了資源

**********************************************/

 

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

void *print_msg_fun(void *ptr)
{
 //pthread_detach(pthread_self());

/*保留此句表示線程處於 分離, 註釋掉表示線程處於非分離*/
 printf("%d/n",pthread_self());


 pthread_exit(NULL);
}

int main()
{
 pthread_t th;

/*1  創建線程*/ 
 while(1)
 {
  if( 0 != (pthread_create(&th,NULL,print_msg_fun,NULL)))
  {
   perror("create th1");
   return;
  }

  sleep(3);
  printf("%d/n",th);
 }

}
通常是主線程使用pthread_create()創建子線程以後,一般可以調用pthread_detach(threadid)分離剛剛創建的子線程,這裏的threadid是指子線程的threadid;如此以來,該子線程止時底層資源立即被回收;
    被創建的子線程也可以自己分離自己,子線程調用pthread_detach(pthread_self())就是分離自己,因爲pthread_self()這個函數返回的就是自己本身的線程ID;

一個可join的線程所佔用的內存僅當有線程對其執行了pthread_join()後纔會釋放,因此爲了避免內存泄漏,所有線程的終止,要麼已設爲DETACHED,要麼就需要使用pthread_join()來回收。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章