Linux中線程使用詳解


線程與進程
為什么有了進程的概念后,還要再引入線程呢?使用多線程到底有哪些好處?什么的系統應該選用多線程?我們首先必須回答這些問題。

  使用多線程的理由之一是和進程相比,它是一種非常"節儉"的多任務操作方式。我們知道,在Linux系統下,啟動一個新的進程必須分配給它獨立的地址空間,建立眾多的數據表來維護它的代碼段、堆棧段和數據段,這是一種"昂貴"的多任務工作方式。而運行於一個進程中的多個線程,它們彼此之間使用相同的地址空間,共享大部分數據,啟動一個線程所花費的空間遠遠小於啟動一個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小於進程間切換所需要的時間。據統計,總的說來,一個進程的開銷大約是一個線程開銷的30倍左右,當然,在具體的系統上,這個數據可能會有較大的區別。

  使用多線程的理由之二是線程間方便的通信機制。對不同進程來說,它們具有獨立的數據空間,要進行數據的傳遞只能通過通信的方式進行,這種方式不僅費時,而且很不方便。線程則不然,由於同一進程下的線程之間共享數據空間,所以一個線程的數據可以直接為其它線程所用,這不僅快捷,而且方便。當然,數據的共享也帶來其他一些問題,有的變量不能同時被兩個線程所修改,有的子程序中聲明為static的數據更有可能給多線程程序帶來災難性的打擊,這些正是編寫多線程程序時最需要注意的地方。

  除了以上所說的優點外,不和進程比較,多線程程序作為一種多任務、並發的工作方式,當然有以下的優點:

  1) 提高應用程序響應。這對圖形界面的程序尤其有意義,當一個操作耗時很長時,整個系統都會等待這個操作,此時程序不會響應鍵盤、鼠標、菜單的操作,而使用多線程技術,將耗時長的操作(time consuming)置於一個新的線程,可以避免這種尷尬的情況。

  2) 使多CPU系統更加有效。操作系統會保證當線程數不大於CPU數目時,不同的線程運行於不同的CPU上。

3) 改善程序結構。一個既長又復雜的進程可以考慮分為多個線程,成為幾個獨立或半獨立的運行部分,這樣的程序會利於理解和修改。

 

一、線程標識

  • 線程有ID, 但不是系統唯一, 而是進程環境中唯一有效.
  • 線程的句柄是pthread_t類型, 該類型不能作為整數處理, 而是一個結構.

下面介紹兩個函數:

  • 頭文件: <pthread.h>
  • 原型: int pthread_equal(pthread_t tid1, pthread_t tid2);
  • 返回值: 相等返回非0, 不相等返回0.
  • 說明: 比較兩個線程ID是否相等.

 

  • 頭文件: <pthread.h>
  • 原型: pthread_t pthread_self();
  • 返回值: 返回調用線程的線程ID.

二、線程創建

 在執行中創建一個線程, 可以為該線程分配它需要做的工作(線程執行函數), 該線程共享進程的資源. 創建線程的函數pthread_create()

  • 頭文件: <pthread.h>
  • 原型: int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(start_rtn)(void), void *restrict arg);
  • 返回值: 成功則返回0, 否則返回錯誤編號.
  • 參數:
    • tidp: 指向新創建線程ID的變量, 作為函數的輸出.
    • attr: 用於定制各種不同的線程屬性, NULL為默認屬性(見下).
    • start_rtn: 函數指針, 為線程開始執行的函數名.該函數可以返回一個void *類型的返回值,而這個返回值也可以是其他類型,並由 pthread_join()獲取
    • arg: 函數的唯一無類型(void)指針參數, 如要傳多個參數, 可以用結構封裝.

linux下多線程程序的編譯方法:

       因為pthread的庫不是linux系統的庫,所以在進行編譯的時候要加上     -lpthread

       # gcc filename -lpthread  //默認情況下gcc使用c庫,要使用額外的庫要這樣選擇使用的庫

 

例1:thread_create.c

 1 #include <stdio.h>
 2 #include <pthread.h>  //包線程要包含
 3 void *mythread1(void)
 4 {
 5    int i;
 6    for(i=0;i<100;i++)
 7    {
 8       printf("this is the 1st pthread,created by zieckey.\n");
 9       sleep(1);
10    }
11 } 
12 void *mythread2(void)
13 {
14     int i;
15 for(i=0;i<100;i++)
16    {
17       printf("this is the 2st pthread,created by zieckey.\n");
18       sleep(1);
19    }
20 }
21 int main()
22 {
23     int ret=0;
24     pthread_tid1,id2;
25    ret=pthread_create(&id1,NULL,(void*)mythread1,NULL);
26     if(ret)
27     {
28         printf("create pthread error!\n");
29          return -1; 
30     }
31    ret=pthread_create(&id2,NULL,(void*)mythread2,NULL);
32     if(ret)
33     {
34         printf("create pthread error!\n");
35          return  -1; 
36     }
37    pthread_join(id1,NULL);
38    pthread_join(id2,NULL);
39  
40     return 0;
41 }
42 編譯步驟:gcc thread_create .c -lpthread -othread_create

例2: thread_int.c  //向線程函數傳遞整形參數

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <unistd.h>
 4 void *create(void *arg)
 5 {
 6     int *num;
 7     num=(int *)arg;
 8    printf("create parameter is %d \n",*num);
 9     return (void *)0;
10 }
11 int main(int argc,char *argv[])
12 {
13    pthread_t tidp;
14     int error;
15     int test=4;
16     int*attr=&test;
17  
18   error=pthread_create(&tidp,NULL,create,(void*)attr);
19     if(error)
20      {
21        printf("pthread_create is created is not created...\n");
22        return -1;
23     }
24    sleep(1);
25   printf("pthread_create is created...\n");
26    return 0;
27 }
28 注:字符串,結構參數,一樣道理

三 線程的合並與分離

 

我們首先要明確的一個問題就是什么是線程的合並。從前面的敘述中讀者們已經了解到了,pthread_create()接口負責創建了一個線程。那么線程也屬於系統的資源,這跟內存沒什么兩樣,而且線程本身也要占據一定的內存空間。眾所周知的一個問題就是C或C++編程中如果要通過malloc()或new分配了一塊內存,就必須使用free()或delete來回收這塊內存,否則就會產生著名的內存泄漏問題。既然線程和內存沒什么兩樣,那么有創建就必須得有回收,否則就會產生另外一個著名的資源泄漏問題,這同樣也是一個嚴重的問題。那么線程的合並就是回收線程資源了。
線程的合並是一種主動回收線程資源的方案。當一個進程或線程調用了針對其它線程的pthread_join()接口,就是線程合並了。這個接口會阻塞調用進程或線程,直到被合並的線程結束為止。當被合並線程結束,pthread_join()接口就會回收這個線程的資源,並將這個線程的返回值返回給合並者。
與線程合並相對應的另外一種線程資源回收機制是線程分離,調用接口是pthread_detach()。線程分離是將線程資源的回收工作交由系統自動來完成,也就是說當被分離的線程結束之后,系統會自動回收它的資源。因為線程分離是啟動系統的自動回收機制,那么程序也就無法獲得被分離線程的返回值,這就使得pthread_detach()接口只要擁有一個參數就行了,那就是被分離線程句柄。
線程合並和線程分離都是用於回收線程資源的,可以根據不同的業務場景酌情使用。不管有什么理由,你都必須選擇其中一種,否則就會引發資源泄漏的問題,這個問題與內存泄漏同樣可怕。
 

4. 線程的屬性

 
前面還說到過線程創建的時候是有屬性的,這個屬性由一個線程屬性對象來描述。線程屬性對象由pthread_attr_init()接口初始化,並由pthread_attr_destory()來銷毀,它們的完整定義是:
[cpp]  view plain  copy
 
  1. int pthread_attr_init(pthread_attr_t *attr);  
  2. int pthread_attr_destory(pthread_attr_t *attr);  
那么線程擁有哪些屬性呢?一般地,Linux下的線程有:綁定屬性、分離屬性、調度屬性、堆棧大小屬性和滿占警戒區大小屬性。下面我們就分別來介紹這些屬性。

4.1 綁定屬性

 
說到這個綁定屬性,就不得不提起另外一個概念:輕進程(Light Weight Process,簡稱LWP)。輕進程和Linux系統的內核線程擁有相同的概念,屬於內核的調度實體。一個輕進程可以控制一個或多個線程。默認情況下,對於一個擁有n個線程的程序,啟動多少輕進程,由哪些輕進程來控制哪些線程由操作系統來控制,這種狀態被稱為非綁定的。那么綁定的含義就很好理解了,只要指定了某個線程“綁”在某個輕進程上,就可以稱之為綁定的了。被綁定的線程具有較高的相應速度,因為操作系統的調度主體是輕進程,綁定線程可以保證在需要的時候它總有一個輕進程可用。綁定屬性就是干這個用的。
設置綁定屬性的接口是pthread_attr_setscope(),它的完整定義是:
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
它有兩個參數,第一個就是線程屬性對象的指針,第二個就是綁定類型,擁有兩個取值:PTHREAD_SCOPE_SYSTEM(綁定的)和PTHREAD_SCOPE_PROCESS(非綁定的)。代碼2演示了這個屬性的使用。
[cpp]  view plain  copy
 
  1. #include <stdio.h>  
  2. #include <pthread.h>  
  3. ……  
  4. int main( int argc, char *argv[] )  
  5. {  
  6.     pthread_attr_t attr;  
  7.     pthread_t th;  
  8.     ……  
  9.     pthread_attr_init( &attr );  
  10.     pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );  
  11.     pthread_create( &th, &attr, thread, NULL );  
  12.     ……  
  13. }  
代碼2設置線程綁定屬性
不知道你是否在這里發現了本文的矛盾之處。就是這個綁定屬性跟我們之前說的NPTL有矛盾之處。在介紹NPTL的時候就說過業界有一種m:n的線程方案,就跟這個綁定屬性有關。但是筆者還說過NPTL因為Linux的“蠢”沒有采取這種方案,而是采用了“1:1”的方案。這也就是說,Linux的線程永遠都是綁定。對,Linux的線程永遠都是綁定的,所以PTHREAD_SCOPE_PROCESS在Linux中不管用,而且會返回ENOTSUP錯誤。
既然Linux並不支持線程的非綁定,為什么還要提供這個接口呢?答案就是兼容!因為Linux的NTPL是號稱POSIX標准兼容的,而綁定屬性正是POSIX標准所要求的,所以提供了這個接口。如果讀者們只是在Linux下編寫多線程程序,可以完全忽略這個屬性。如果哪天你遇到了支持這種特性的系統,別忘了我曾經跟你說起過這玩意兒:)

4.2 分離屬性

 
前面說過線程能夠被合並和分離,分離屬性就是讓線程在創建之前就決定它應該是分離的。如果設置了這個屬性,就沒有必要調用pthread_join()或pthread_detach()來回收線程資源了。
設置分離屬性的接口是pthread_attr_setdetachstate(),它的完整定義是:
 
[cpp]  view plain  copy
 
  1. pthread_attr_setdetachstat(pthread_attr_t *attr, int detachstate);  
它的第二個參數有兩個取值:PTHREAD_CREATE_DETACHED(分離的)和PTHREAD_CREATE_JOINABLE(可合並的,也是默認屬性)。代碼3演示了這個屬性的使用。
[cpp]  view plain  copy
 
  1. #include <stdio.h>  
  2. #include <pthread.h>  
  3. ……  
  4. int main( int argc, char *argv[] )  
  5. {  
  6.     pthread_attr_t attr;  
  7.     pthread_t th;  
  8.     ……  
  9.     pthread_attr_init( &attr );  
  10.     pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );  
  11.     pthread_create( &th, &attr, thread, NULL );  
  12.     ……  
  13. }  
 
代碼3設置線程分離屬性
 

4.3 調度屬性

 
線程的調度屬性有三個,分別是:算法、優先級和繼承權。
Linux提供的線程調度算法有三個:輪詢、先進先出和其它。其中輪詢和先進先出調度算法是POSIX標准所規定,而其他則代表采用Linux自己認為更合適的調度算法,所以默認的調度算法也就是其它了。輪詢和先進先出調度算法都屬於實時調度算法。輪詢指的是時間片輪轉,當線程的時間片用完,系統將重新分配時間片,並將它放置在就緒隊列尾部,這樣可以保證具有相同優先級的輪詢任務獲得公平的CPU占用時間;先進先出就是先到先服務,一旦線程占用了CPU則一直運行,直到有更高優先級的線程出現或自己放棄。
設置線程調度算法的接口是pthread_attr_setschedpolicy(),它的完整定義是:
 
pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
 
它的第二個參數有三個取值:SCHED_RR(輪詢)、SCHED_FIFO(先進先出)和SCHED_OTHER(其它)。
Linux的線程優先級與進程的優先級不一樣,進程優先級我們后面再說。Linux的線程優先級是從1到99的數值,數值越大代表優先級越高。而且要注意的是,只有采用SHCED_RR或SCHED_FIFO調度算法時,優先級才有效。對於采用SCHED_OTHER調度算法的線程,其優先級恆為0。
設置線程優先級的接口是pthread_attr_setschedparam(),它的完整定義是:
 
[cpp]  view plain  copy
 
  1. struct sched_param {  
  2.     int sched_priority;  
  3. }  
  4. int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *param);  
sched_param結構體的sched_priority字段就是線程的優先級了。
此外,即便采用SCHED_RR或SCHED_FIFO調度算法,線程優先級也不是隨便就能設置的。首先,進程必須是以root賬號運行的;其次,還需要放棄線程的繼承權。什么是繼承權呢?就是當創建新的線程時,新線程要繼承父線程(創建者線程)的調度屬性。如果不希望新線程繼承父線程的調度屬性,就要放棄繼承權。
設置線程繼承權的接口是pthread_attr_setinheritsched(),它的完整定義是:
 
[cpp]  view plain  copy
 
  1. int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);  
 
它的第二個參數有兩個取值:PTHREAD_INHERIT_SCHED(擁有繼承權)和PTHREAD_EXPLICIT_SCHED(放棄繼承權)。新線程在默認情況下是擁有繼承權。
代碼4能夠演示不同調度算法和不同優先級下各線程的行為,同時也展示如何修改線程的調度屬性。
[cpp]  view plain  copy
 
  1. #include <stdio.h>  
  2. #include <unistd.h>  
  3. #include <stdlib.h>  
  4. #include <pthread.h>  
  5. #define THREAD_COUNT 12  
  6. void show_thread_policy( int threadno )  
  7. {  
  8.     int policy;  
  9.     struct sched_param param;  
  10.     pthread_getschedparam( pthread_self(), &policy, ¶m );  
  11.     switch( policy ){  
  12.     case SCHED_OTHER:  
  13.         printf( "SCHED_OTHER %d\n", threadno );  
  14.         break;  
  15.     case SCHED_RR:  
  16.         printf( "SCHDE_RR %d\n", threadno );  
  17.         break;  
  18.     case SCHED_FIFO:  
  19.         printf( "SCHED_FIFO %d\n", threadno );  
  20.         break;  
  21.     default:  
  22.         printf( "UNKNOWN\n");  
  23.     }  
  24. }  
  25. void* thread( void *arg )  
  26. {  
  27.     int i, j;  
  28.     long threadno = (long)arg;  
  29.     printf( "thread %d start\n", threadno );  
  30.     sleep(1);  
  31.     show_thread_policy( threadno );  
  32.     for( i = 0; i < 10; ++i ) {  
  33.         for( j = 0; j < 100000000; ++j ){}  
  34.         printf( "thread %d\n", threadno );  
  35.     }  
  36.     printf( "thread %d exit\n", threadno );  
  37.     return NULL;  
  38. }  
  39. int main( int argc, char *argv[] )  
  40. {  
  41.     long i;  
  42.     pthread_attr_t attr[THREAD_COUNT];  
  43.     pthread_t pth[THREAD_COUNT];  
  44.     struct sched_param param;  
  45.     for( i = 0; i < THREAD_COUNT; ++i )  
  46.         pthread_attr_init( &attr[i] );  
  47.         for( i = 0; i < THREAD_COUNT / 2; ++i ) {  
  48.             param.sched_priority = 10;                    
  49.             pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );  
  50.             pthread_attr_setschedparam( &attr[i], ¶m );  
  51.             pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );  
  52.         }  
  53.         for( i = THREAD_COUNT / 2; i < THREAD_COUNT; ++i ) {  
  54.             param.sched_priority = 20;                    
  55.             pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );  
  56.             pthread_attr_setschedparam( &attr[i], ¶m );  
  57.             pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );  
  58.         }  
  59.         for( i = 0; i < THREAD_COUNT; ++i )                      
  60.             pthread_create( &pth[i], &attr[i], thread, (void*)i );                
  61.         for( i = 0; i < THREAD_COUNT; ++i )                      
  62.             pthread_join( pth[i], NULL );                      
  63.         for( i = 0; i < THREAD_COUNT; ++i )                      
  64.             pthread_attr_destroy( &attr[i] );                     
  65.     return 0;                             
  66. }  
代碼4設置線程調度屬性
這段代碼中含有一些沒有介紹過的接口,讀者們可以使用Linux的聯機幫助來查看它們的具體用法和作用。

4.4 堆棧大小屬性

 
從前面的這些例子中可以了解到,線程的主函數與程序的主函數main()有一個很相似的特性,那就是可以擁有局部變量。雖然同一個進程的線程之間是共享內存空間的,但是它的局部變量確並不共享。原因就是局部變量存儲在堆棧中,而不同的線程擁有不同的堆棧。Linux系統為每個線程默認分配了8MB的堆棧空間,如果覺得這個空間不夠用,可以通過修改線程的堆棧大小屬性進行擴容。
修改線程堆棧大小屬性的接口是pthread_attr_setstacksize(),它的完整定義為:
[cpp]  view plain  copy
 
  1. int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);  
它的第二個參數就是堆棧大小了,以字節為單位。需要注意的是,線程堆棧不能小於16KB,而且盡量按4KB(32位系統)或2MB(64位系統)的整數倍分配,也就是內存頁面大小的整數倍。此外,修改線程堆棧大小是有風險的,如果你不清楚你在做什么,最好別動它(其實我很后悔把這么危險的東西告訴了你:)。

4.5 滿棧警戒區屬性

 
既然線程是有堆棧的,而且還有大小限制,那么就一定會出現將堆棧用滿的情況。線程的堆棧用滿是非常危險的事情,因為這可能會導致對內核空間的破壞,一旦被有心人士所利用,后果也不堪設想。為了防治這類事情的發生,Linux為線程堆棧設置了一個滿棧警戒區。這個區域一般就是一個頁面,屬於線程堆棧的一個擴展區域。一旦有代碼訪問了這個區域,就會發出SIGSEGV信號進行通知。
雖然滿棧警戒區可以起到安全作用,但是也有弊病,就是會白白浪費掉內存空間,對於內存緊張的系統會使系統變得很慢。所有就有了關閉這個警戒區的需求。同時,如果我們修改了線程堆棧的大小,那么系統會認為我們會自己管理堆棧,也會將警戒區取消掉,如果有需要就要開啟它。
修改滿棧警戒區屬性的接口是pthread_attr_setguardsize(),它的完整定義為:
[cpp]  view plain  copy
 
  1. int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);  
它的第二個參數就是警戒區大小了,以字節為單位。與設置線程堆棧大小屬性相仿,應該盡量按照4KB或2MB的整數倍來分配。當設置警戒區大小為0時,就關閉了這個警戒區。
雖然棧滿警戒區需要浪費掉一點內存,但是能夠極大的提高安全性,所以這點損失是值得的。而且一旦修改了線程堆棧的大小,一定要記得同時設置這個警戒區。
 
五、線程終止
 
pthread_exit函數:
  • 原型: void pthread_exit(void *rval_ptr);
  • 頭文件: <pthread.h>
  • 參數: rval_ptr是一個無類型指針, 指向線程的返回值存儲變量.

pthread_join函數:

  • 原型: int pthread_join(pthread_t thread, void **rval_ptr);
  • 頭文件: <pthread.h>
  • 返回值: 成功則返回0, 否則返回錯誤編號.
  • 參數:
    • thread: 線程ID.
    • rval_ptr: 指向返回值的指針(返回值也是個指針).
  • 說明:
    • 調用線程將一直阻塞, 直到指定的線程調用pthread_exit, 從啟動例程返回或被取消.
    • 如果線程從它的啟動例程返回, rval_ptr包含返回碼.
    • 如果線程被取消, 由rval_ptr指定的內存單元置為: PTHREAD_CANCELED.
    • 如果對返回值不關心, 可把rval_ptr設為NULL.

實例:

 1 #include <pthread.h>
 2 #include <stdio.h>
 3  
 4 /* print process and thread IDs */
 5 void printids(const char *s)
 6 {
 7    pid_t pid, ppid;
 8    pthread_t tid;
 9      pid= getpid();
10    ppid = getppid();
11    tid = pthread_self();
12     printf("%16s pid %5u ppid %5u tid %16u (0x%x) ",
13             s, (unsigned int)pid, (unsigned int)ppid,
14             (unsigned int)tid, (unsigned int)tid);
15 }
16  /* thread process */
17 void *thread_func(void *arg);
18 {
19    printids("new thread: ");
20    return (void *)108;
21 }
22  /* main func */
23 int main()
24 {
25    int err;
26    void *tret; /* thread return value */
27    pthread_t ntid;
28    err = pthread_create(&ntid, NULL, thread_func, NULL);
29    if (err != 0)
30        perror("can't create thread");
31  
32    err = pthread_join(ntid, &tret);
33    if (err != 0)
34        perror("can't join thread");
35     printids("main thread: ");
36    printf("thread exit code: %d ", (int)tret);
37    sleep(1);
38    return 0;
39 }

 

pthread_cancel函數:

pthread_cancel函數發送終止信號

pthread_setcancelstate函數設置終止方式

pthread_testcancel函數取消線程(另一功能是:設置取消點)

1) 線程取消的定義
一般情況下,線程在其主體函數退出的時候會自動終止,但同時也可以因為接收到另一個線程發來的終止(取消)請求而強制終止。

 

2) 線程取消的語義
線程取消的方法是向目標線程發Cancel信號(pthread_cancel函數發送Cancel信號),但如何處理Cancel信號則由目標線程自己決定,或者忽略、或者立即終止、或者繼續運行至Cancelation-point(取消點),由不同的Cancelation狀態(pthread_setcancelstate函數設置狀態)決定。

 

線程接收到CANCEL信號的缺省處理(即pthread_create()創建線程的缺省狀態)是繼續運行至取消點,也就是說設置一個CANCELED狀態,線程繼續運行,只有運行至Cancelation-point的時候才會退出。

 

3 )取消點
根據POSIX標准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、 pthread_cond_timedwait()、sem_wait()、sigwait()等函數以及read()、write()等會引起阻塞的系統調用都是Cancelation-point,而其他pthread函數都不會引起Cancelation動作。但是pthread_cancel的手冊頁聲稱,由於LinuxThread庫與C庫結合得不好,因而目前C庫函數都不是Cancelation-point;但CANCEL信號會使線程從阻塞的系統調用中退出,並置EINTR錯誤碼,因此可以在需要作為Cancelation-point的系統調用前后調用 pthread_testcancel(),從而達到POSIX標准所要求的目標,即如下代碼段:

 

	pthread_testcancel();

        
	retcode = read(fd, buffer, length);

        pthread_testcancel();

 



4 )程序設計方面的考慮
如果線程處於無限循環中,且循環體內沒有執行至取消點的必然路徑,則線程無法由外部其他線程的取消請求而終止。因此在這樣的循環體的必經路徑上應該加入pthread_testcancel()調用。

 

5 )與線程取消相關的pthread函數
int pthread_cancel(pthread_t thread)
發送終止信號給thread線程,如果成功則返回0,否則為非0值。發送成功並不意味着thread會終止。

 

int pthread_setcancelstate(int state, int *oldstate)
設置本線程對Cancel信號的反應,state有兩種值:PTHREAD_CANCEL_ENABLE(缺省)和 PTHREAD_CANCEL_DISABLE,分別表示收到信號后設為CANCLED狀態和忽略CANCEL信號繼續運行;old_state如果不為 NULL則存入原來的Cancel狀態以便恢復。

 

int pthread_setcanceltype(int type, int *oldtype)
設置本線程取消動作的執行時機,type由兩種取值:PTHREAD_CANCEL_DEFFERED和 PTHREAD_CANCEL_ASYCHRONOUS,僅當Cancel狀態為Enable時有效,分別表示收到信號后繼續運行至下一個取消點再退出和立即執行取消動作(退出);oldtype如果不為NULL則存入運來的取消動作類型值。

 

void pthread_testcancel(void)
功能一:設置取消點;

功能二:檢查本線程是否處於Canceld狀態,如果是,則進行取消動作,否則直接返回。

 

代碼:

  1 #include <stdio.h>
  2 #include <errno.h>
  3 #include <unistd.h>
  4 #include <stdlib.h>
  5 #include <pthread.h>
  6 
  7 
  8 #define THREAD_MAX 4
  9 
 10 
 11 pthread_mutex_t mutex;
 12 pthread_t thread[THREAD_MAX];
 13 
 14 
 15 static int tries;
 16 static int started;
 17 
 18 
 19 void print_it(int *arg)
 20 {
 21 pthread_t tid;
 22 tid = pthread_self();
 23 printf("Thread %lx was canceled on its %d try.\n",tid,*arg);
 24 }
 25 
 26 
 27 void *Search_Num(int arg)
 28 {
 29 pthread_t tid;
 30 int num;
 31 int k=0,h=0,j;
 32 int ntries;
 33 tid = pthread_self();
 34 
 35 /*while(pthread_mutex_trylock(&mutex) == EBUSY)
 36 {
 37 printf("**************busy****************\n");
 38 pthread_testcancel();
 39 }*/
 40 srand(arg);
 41 num = rand()&0xFFFFFF;
 42 //pthread_mutex_unlock(&mutex);
 43 
 44 printf("thread num %lx\n",tid);
 45 
 46 ntries = 0;
 47 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
 48 pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
 49 
 50 pthread_cleanup_push((void *)print_it,(void *)&ntries);
 51 
 52 while(1)
 53 {
 54 num = (num+1)&0xffffff;
 55 ntries++;
 56 
 57 if(arg == num)
 58 {
 59 //只允許一個線程操作此處
 60 while(pthread_mutex_trylock(&mutex) == EBUSY) { 
 61 //一個線程操作后其余線程進入次循環掛起,等待pthread_cancel函數發送cancel信號終止線程
 62 k++;
 63 if(k == 10000)
 64 {
 65 printf("----------2busy2-----------\n");
 66 }
 67 
 68 pthread_testcancel();
 69 }
 70 tries = ntries;
 71 //pthread_mutex_unlock(&mutex);   //如果加上這句話,將會有好幾個線程找到主函數中設定的值pid
 72 printf("Thread %lx found the number!\n",tid);
 73 
 74 for(j = 0;j<THREAD_MAX;j++)
 75 {
 76 if(thread[j]!=tid)
 77 {
 78 pthread_cancel(thread[j]);
 79 }
 80 }
 81 
 82 break;
 83 }
 84 if(ntries%100 == 0)
 85 {
 86 h++;
 87 /*線程阻塞,其他線程爭奪資源,或者是等待pthread_cancel函數發送cancel信號終止線程*/
 88 pthread_testcancel();
 89 /*這是為了弄明白pthread_testcancel函數的作用而設置的代碼段*/
 90 if(h == 10000)
 91 {
 92 h = 0;
 93 printf("----------thread num %lx-------------\n",tid);
 94 }
 95 }
 96 }
 97 pthread_cleanup_pop(0);
 98 return (void *)0;
 99 }
100 
101 
102 int main()
103 {
104 int i,pid;
105 
106 pid = getpid(); //設置要查找的數
107 
108 pthread_mutex_init(&mutex,NULL);
109 printf("Search the num of %d\n",pid);
110 for(started = 0; started < THREAD_MAX; started++)
111 {
112 pthread_create(&thread[started],NULL,(void *)Search_Num,(void *)pid);
113 }
114 
115 for(i = 0; i < THREAD_MAX; i++)
116 {
117 printf("-----------i = %d--------------\n",i);
118 pthread_join(thread[i],NULL);
119 }
120 printf("It took %d tries ot find the number!\n",tries);
121 return 0;
122 }
123 運行結果:
124 Search the num of 6531
125 -----------i = 0--------------
126 thread num b6fbcb70
127 thread num b67bbb70
128 thread num b5fbab70
129 thread num b77bdb70
130 ----------thread num b67bbb70-------------
131 Thread b67bbb70 found the number!
132 ----------thread num b6fbcb70-------------
133 ----------thread num b77bdb70-------------
134 ----------2busy2-----------
135 ----------thread num b5fbab70-------------
136 ----------2busy2-----------
137 Thread b5fbab70 was canceled on its 1174527 try.
138 Thread b77bdb70 was canceled on its 1023100 try.
139 -----------i = 1--------------
140 Thread b6fbcb70 was canceled on its 1174527 try.
141 -----------i = 2--------------
142 -----------i = 3--------------
143 It took 1174527 tries ot find the number!

 

void pthread_cleanup_push(void (*rtn)(void *),void *arg);
void pthread_cleanup_pop(int execute);
當線程執行以下動作時調用清理函數,調用參數為arg,清理函數rtn的調用順序是由pthread_cleanup_push函數來安排的。
1.調用pthread_exit時。2.響應取消請求時。3.用非零execute參數調用pthread_cleanup_pop時。
如果execute參數置為0,清理函數將不被調用。無論哪種情況,pthread_cleanup_pop都將刪除上次pthread_clean_push調用建立的清理處理程序。
實例:
 1 <span style="font-size: small;">#include <stdlib.h>  
 2 #include <stdio.h>  
 3 #include <pthread.h>  
 4   
 5 void cleanup(void *arg)  
 6 {  
 7     printf("cleanup: %s\n", (char *)arg);  
 8 }  
 9   
10 void *thr_fn1(void *arg)  
11 {  
12     printf("thread 1 start\n");  
13     pthread_cleanup_push(cleanup, "thread 1 first handler");  
14     pthread_cleanup_push(cleanup, "thread 1 second handler");  
15     printf("thread 1 push complete\n");  
16     if (arg)  
17         return((void *)1);  
18         // pthread_exit((void *)2);  
19           
20     pthread_cleanup_pop(0);  
21     pthread_cleanup_pop(0);  
22     // return((void *)1);  
23     pthread_exit((void *)2);  
24   
25 }  
26   
27 void *thr_fn2(void *arg)  
28 {  
29     printf("thread 2 start\n");  
30     pthread_cleanup_push(cleanup, "thread 2 first handler");  
31     pthread_cleanup_push(cleanup, "thread 2 second handler");  
32     printf("thread 2 push complete\n");  
33     if (arg)  
34         pthread_exit((void *)2);  
35     pthread_cleanup_pop(0);  
36     pthread_cleanup_pop(0);  
37     pthread_exit((void *)2);  
38 }  
39   
40 int main(void)  
41 {  
42     int         err;  
43     pthread_t   tid1, tid2;  
44     void        *tret;  
45   
46     err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);  
47     if (err != 0)  
48         printf("can't create thread 1: %c\n", strerror(err));  
49     err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);  
50     if (err != 0)  
51         printf("can't create thread 2: %c\n", strerror(err));  
52     err = pthread_join(tid1, &tret);  
53     if (err != 0)  
54         printf("can't join with thread 1: %c\n", strerror(err));  
55     printf("thread 1 exit code %d\n", (int)tret);  
56     err = pthread_join(tid2, &tret);  
57     if (err != 0)  
58         printf("can't join with thread 2: %c\n", strerror(err));  
59     printf("thread 2 exit code %d\n", (int)tret);  
60     exit(0);  
61 }  
62 </span>  

 

pthread_detach()函數:

 

創建一個線程默認的狀態是joinable。

如果一個線程結束運行但沒有被join,則它的狀態類似於進程中的Zombie Process,即還有一部分資源沒有被回收(退出狀態碼).

所以創建線程者應該調用pthread_join來等待線程運行結束,並可得到線程的退出代 碼,回收其資源(類似於wait,waitpid) 。

但是調用pthread_join(pthread_id)后,如果該線程沒有運行結束,調用者會被阻塞,在有些情況下我們並不希望如此。

比如在Web服務器中當主線程為每個新來的鏈接創建一個子線程進行處理的時候,主線程並不希望因為調用pthread_join而阻塞(因為還要繼續處理之后到來的鏈接),這時可以

1)在子線程中加入代碼pthread_detach(pthread_self())

2)父線程調用pthread_detach(thread_id)(非阻塞,可立即返回)   

這將該子線程的狀態設置為detached,則該線程運行結束后會自動釋放所有資源。

 

pthread_kill()函數:

 

pthread_kill與kill有區別,是向線程發送signal。,大部分signal的默認動作是終止進程的運行,所以,我們才要用signal()去抓信號並加上處理函數。

int pthread_kill(pthread_t thread, int sig);

向指定ID的線程發送sig信號,如果線程代碼內不做處理,則按照信號默認的行為影響整個進程,也就是說,如果你給一個線程發送了SIGQUIT,但線程卻沒有實現signal處理函數,則整個進程退出

pthread_kill(threadid, SIGKILL)殺死整個進程。 如果要獲得正確的行為,就需要在線程內實現signal(SIGKILL,sig_handler)。所以,如果int sig的參數不是0,那一定要清楚到底要干什么,而且一定要實現線程的信號處理函數,否則,就會影響整個進程。

如果int sig是0呢,這是一個保留信號,一個作用是用來判斷線程是不是還活着。pthread_kill的返回值: 成功:0 線程不存在:ESRCH 信號不合法:EINVAL

代碼:

復制代碼
int kill_rc = pthread_kill(thread_id,0);

if(kill_rc == ESRCH) 
        printf("the specified thread did not exists or already quit\n"); 

else if(kill_rc == EINVAL) 
        printf("signal is invalid\n"); 
else 
        printf("the specified thread is alive\n");
復制代碼

 

這里附上線程基本函數:

------------------------------------------------------------------------------------------
                     POSIX函數                                                    描述
-------------------------------------------------------------------------------------------
                    pthread_create                                    創建一個線程
                    pthread_self                                        找出自己的線程ID
                    pthread_equal                                     測試2個線程ID是否相等
                    pthread_detach                                   設置線程以釋放資源
                    pthread_join                                        等待一個線程
                    pthread_cancel                                    終止另一個線程
                    pthread_exit                                        退出線程,而不退出進程
                    pthread_kill                                         向線程發送一個信號
-------------------------------------------------------------------------------------------
 
 
 

線程屬性pthread_attr_t簡介

Posix線程中的線程屬性pthread_attr_t主要包括scope屬性、detach屬性、堆棧地址、堆棧大小、優先級。在pthread_create中,把第二個參數設置為NULL的話,將采用默認的屬性配置。
pthread_attr_t的主要屬性的意義如下:
__detachstate,表示新線程是否與進程中其他線程脫離同步, 如果設置為PTHREAD_CREATE_DETACHED 則新線程不能用pthread_join()來同步,且在退出時自行釋放所占用的資源。缺省為PTHREAD_CREATE_JOINABLE狀態。這個屬性也可以在線程創建並運行以后用pthread_detach()來設置,而一旦設置為PTHREAD_CREATE_DETACH狀態(不論是創建時設置還是運行時設置)則不能再恢復到PTHREAD_CREATE_JOINABLE狀態。
__schedpolicy,表示新線程的調度策略,主要包括SCHED_OTHER(正常、非實時)、SCHED_RR(實時、輪轉法)和SCHED_FIFO(實時、先入先出)三種,缺省為SCHED_OTHER,后兩種調度策略僅對超級用戶有效。運行時可以用過pthread_setschedparam()來改變。
__schedparam,一個struct sched_param結構,目前僅有一個sched_priority整型變量表示線程的運行優先級。這個參數僅當調度策略為實時(即SCHED_RR或SCHED_FIFO)時才有效,並可以在運行時通過pthread_setschedparam()函數來改變,缺省為0。
__inheritsched,有兩種值可供選擇:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新線程使用顯式指定調度策略和調度參數(即attr中的值),而后者表示繼承調用者線程的值。缺省為PTHREAD_EXPLICIT_SCHED。
__scope,表示線程間競爭CPU的范圍,也就是說線程優先級的有效范圍。POSIX的標准中定義了兩個值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示與系統中所有線程一起競爭CPU時間,后者表示僅與同進程中的線程競爭CPU。目前LinuxThreads僅實現了PTHREAD_SCOPE_SYSTEM一值。
  為了設置這些屬性,POSIX定義了一系列屬性設置函數,包括pthread_attr_init()、pthread_attr_destroy()和與各個屬性相關的pthread_attr_getXXX/pthread_attr_setXXX函數。
在設置線程屬性 pthread_attr_t 之前,通常先調用pthread_attr_init來初始化,之后來調用相應的屬性設置函數。
主要的函數如下:
1、pthread_attr_init
功能:        對線程屬性變量的初始化。
頭文件:     <pthread.h>
函數原型:   int pthread_attr_init (pthread_attr_t* attr);
函數傳入值:attr:線程屬性。
函數返回值:成功: 0
                失敗: -1
2、pthread_attr_setscope
功能:        設置線程 __scope 屬性。scope屬性表示線程間競爭CPU的范圍,也就是說線程優先級的有效范圍。POSIX的標准中定義了兩個值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示與系統中所有線程一起競爭CPU時間,后者表示僅與同進程中的線程競爭CPU。默認為PTHREAD_SCOPE_PROCESS。目前LinuxThreads僅實現了PTHREAD_SCOPE_SYSTEM一值。
頭文件:     <pthread.h>
函數原型:   int pthread_attr_setscope (pthread_attr_t* attr, int scope);
函數傳入值:attr: 線程屬性。
                      scope:PTHREAD_SCOPE_SYSTEM,表示與系統中所有線程一起競爭CPU時間,
                                 PTHREAD_SCOPE_PROCESS,表示僅與同進程中的線程競爭CPU
函數返回值得:同1。
3、pthread_attr_setdetachstate
功能:        設置線程detachstate屬性。該表示新線程是否與進程中其他線程脫離同步,如果設置為PTHREAD_CREATE_DETACHED則新線程不能用pthread_join()來同步,且在退出時自行釋放所占用的資源。缺省為PTHREAD_CREATE_JOINABLE狀態。這個屬性也可以在線程創建並運行以后用pthread_detach()來設置,而一旦設置為PTHREAD_CREATE_DETACH狀態(不論是創建時設置還是運行時設置)則不能再恢復到PTHREAD_CREATE_JOINABLE狀態。
頭文件:      <phread.h>
函數原型:    int pthread_attr_setdetachstate (pthread_attr_t* attr, int detachstate);
函數傳入值:attr:線程屬性。
detachstate:PTHREAD_CREATE_DETACHED,不能用pthread_join()來同步,且在退出時自行釋放所占用的資源
                    PTHREAD_CREATE_JOINABLE,能用pthread_join()來同步
函數返回值得:同1。
4、pthread_attr_setschedparam
功能:       設置線程schedparam屬性,即調用的優先級。
頭文件:     <pthread.h>
函數原型:   int pthread_attr_setschedparam (pthread_attr_t* attr, struct sched_param* param);
函數傳入值:attr:線程屬性。
                 param:線程優先級。一個struct sched_param結構,目前僅有一個sched_priority整型變量表示線程的運行優先級。這個參數僅當調度策略為實時(即SCHED_RR或SCHED_FIFO)時才有效,並可以在運行時通過pthread_setschedparam()函數來改變,缺省為0
函數返回值:同1。
5、pthread_attr_getschedparam
功能:       得到線程優先級。
頭文件:    <pthread.h>
函數原型:  int pthread_attr_getschedparam (pthread_attr_t* attr, struct sched_param* param);
函數傳入值:attr:線程屬性;
                    param:線程優先級;
函數返回值:同1。
 
例:
 1 #include <stdlib.h>   
 2 #include <stdio.h>   
 3 #include <errno.h>   
 4 #include <pthread.h>   
 5 static void pthread_func_1 (void);   
 6 static void pthread_func_2 (void);   
 7   
 8 int main (int argc, char** argv)   
 9 {   
10   pthread_t pt_1 = 0;   
11   pthread_t pt_2 = 0;   
12   pthread_attr_t atrr = {0};   
13   int ret = 0;   
14   
15 /*初始化屬性線程屬性*/
16   pthread_attr_init (&attr);   
17   pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);   
18   pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);   
19      
20   ret = pthread_create (&pt_1, &attr, pthread_func_1, NULL);   
21   if (ret != 0)   
22   {   
23     perror ("pthread_1_create");   
24   }   
25      
26   ret = pthread_create (&pt_2, NULL, pthread_func_2, NULL);   
27   if (ret != 0)   
28   {   
29     perror ("pthread_2_create");   
30   }   
31   
32   pthread_join (pt_2, NULL);   
33   
34   return 0;   
35 }   
36   
37 static void pthread_func_1 (void)   
38 {   
39   int i = 0;   
40      
41   for (; i < 6; i++)   
42   {    
43     printf ("This is pthread_1.\n");   
44       
45     if (i == 2)   
46     {   
47       pthread_exit (0);   
48     }   
49   }   
50   
51   return;   
52 }   
53   
54 static void pthread_func_2 (void)   
55 {   
56   int i = 0;   
57   
58   for (; i < 3; i ++)   
59   {   
60     printf ("This is pthread_2.\n");   
61   }   
62   
63   return;   
64 }  

 

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM