線程包含了表示進程內執行環境必需的信息,其中包括進程中標示線程的線程ID、一組寄存器值、棧、調度優先級和策略、信號屏蔽字、errno變量以及線程私有數據。
進程的所有信息對該進程的所有線程都是共享的,包括可執行的程序文本、程序的全局內存和堆內存、棧以及文件描述符。
線程標識:
進程ID在整個系統中是唯一的,但線程ID不同,線程ID只在它所屬的進程環境中有效。進程ID的數據結構為pid_t,線程ID的數據結構為pthread_t。
比較兩個線程ID是否相等:
#include <pthread.h> int pthread_equal(pthread_t tid1,pthread_t tid2);//返回值:若相等返回非0值,否則返回0
線程可以通過調用pthread_self函數獲取自身的線程ID:
#include <pthread.h> pthread_t pthread_self(void); //返回值:調用線程的線程ID
線程的創建:
#include <pthread.h> int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,void *(*start_rtn)(void *),void * arg);//返回值:若成功則返回0,否則返回錯誤編號
當pthread_create成功返回時,由tidp指向的內存單元被設置為新創建線程的線程ID。attr參數用於定制各種不同的線程屬性。若設置為NULL,表示創建默認屬性的線程。
新創建的線程從start_rtn函數的地址開始運行,該函數只有一個無類型指針參數的arg,如果需要向start_rtn函數傳遞的參數不止一個,那麼需要把這些參數放到一個結構中,然後把這個結構的地址作為arg參數傳入。
線程創建時並不能保證哪個線程會先運行:是新創建的線程還是調用線程。新創建的線程可以訪問進程地址空間,並且繼承調用線程的浮點環境和信號屏蔽字,但是該線程的未決信號集被清除。
每個線程都提供了errno的副本。
注意: pthread_create不是等待start_rtn函數運行完成後才返回的。一般情況下是pthread_create函數先返回,不過有時候start_rtn函數先運行完成後,pthread_create才返回。它們之間沒有先後順序。
示例代碼:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <sys/types.h> #include <unistd.h> pthread_t ntid; void printids( const char *s) { pid_t pid; pthread_t tid; pid = getpid(); tid = pthread_self(); printf("%s pid = %u pthread_t = %u\n",s,(unsigned int)pid, (unsigned int)tid); } void* thr_fn(void * arg) { printids("new pthread: "); return NULL; } int main(int argc,char **argv) { int err; err = pthread_create(&ntid,NULL,thr_fn,NULL); if ( err != 0 ) perror("pthread_create"); printids("main thread: "); sleep(1); exit(0); }
運行結果:
huangcheng@ubuntu:~$ ./a.out new pthread ID = 3077581680 main thread: pid = 2458 pthread_t = 3077584576 new pthread: pid = 2458 pthread_t = 3077581680
注意在本例裡,主線程把新線程ID存放在ntid中,但是新建的線程並不能安全的使用它,如果新線程在主線程調用pthread_create返回之前就運行了,那麼新線程看到的是未經初始化的ntid的內容,這個內容並不是正確的線程ID。
線程終止:
如果進程中的任一線程調用了exit、_Exit或者_exit,那麼整個進程就會終止。與此類似,如果信號的默認動作是終止進程,那麼把該信號發送到線程會終止整個進程。
單個線程可以通過下列三種方式退出,在不終止整個進程的情況下停止它的控制流:
線程只是從啟動歷程中返回,返回值是線程的退出碼。
線程可以被同一進程中的其他線程取消。
線程調用pthread_exit.
注意:主線程調用pthread_exit也不會導致整個進程結束,其子線程還是繼續運行。
#include <pthread.h> void pthread_exit(void* rval_ptr);
rval_ptr是一個無類型的指針。進程中的其他線程可以通過調用pthread_join函數返回到這個指針。
#include <pthread.h> int pthread_join(pthread_t thread,void **rval_ptr);//返回值:若成功則返回0 ,否則返回錯誤編號
調用線程將一直阻塞,直到指定的線程調用pthread_exit、從啟動例程中返回或者被取消。
如果線程只是從它的啟動例程返回,rval_ptr將包含返回碼。如果線程被取消,由rval_ptr指定的內存單元就置為PTHREAD_CANCELED。
可以通過調用pthread_join自動把線程置於分離狀態,這樣資源就可以恢復。如果線程已經處於分離狀態,pthread_join調用就會失敗,返回EINVAL。
線程可以通過調用pthread_cancel函數來請求取消同一進程中的其他線程:
#include <pthread.h> int pthread_cancel(pthread_t tif); // 返回值:若成功則返回0,否則返回錯誤編號
在默認情況下,pthread_cancel函數會使得由tid標識的線程的行為表現為如同調用了參數為PTHREAD_CANCEL的pthread_exit函數,但是線程可以選擇忽略取消方式或是控制取消方式。注意:pthread_cancel並不等待線程終止,它僅僅提出請求。
清理函數:
#include <pthread.h> void pthread_cleanup_push(void (*rtn)(void*),void *arg); void pthread_cleanup_pop(int execute);
線程可以安排它退出時需要調用的函數,這與進程可以用atexit函數安排進程退出需要調用的函數時類似的。這樣的函數稱為線程清理函數處理程序。線程可以建立多個清理處理程序。處理程序記錄在棧中的,也就是說它們的執行順序與它們注冊時的順序相反。
pthread_cleanup_push()/pthread_cleanup_pop()采用先入後出的棧結構管理,void routine(void *arg)函數在調用pthread_cleanup_push()時壓入清理函數棧,多次對pthread_cleanup_push() 的調用將在清理函數棧中形成一個函數鏈;從pthread_cleanup_push的調用點到pthread_cleanup_pop之間的程序段中的終止動作(包括調用pthread_exit()和異常終止,不包括return)都將執行pthread_cleanup_push()所指定的清理函數。
pthread_cleanup_push()函數執行壓棧清理函數的操作,而pthread_cleanup_pop()函數執行從棧中刪除清理函數的操作。
注意pthread_cleanup_pop不管參數是零還是非零,都會從棧頂刪除一個清理函數。
在下面三種情況下,pthread_cleanup_push()壓棧的“清理函數”會被調用,調用參數為arg,清理函數rtn的調用順序是由pthread_cleanup_push函數來安排的。:
線程調用pthread_exit()函數,而不是直接return.
響應取消請求時,也就是有其它的線程對該線程調用pthread_cancel()函數。
本線程調用pthread_cleanup_pop()函數,並且其參數非0.
如果execute參數置為0,清理函數將不被調用。無論哪種情況即不管pthread_cleanup_pop的參數時零還是非零,pthread_cleanup_pop都將刪除上次pthread_cleanup_push調用建立的清理處理程序,即刪除清理函數鏈的棧頂。
pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式實現的,這是pthread.h中的宏定義:
#define pthread_cleanup_push(routine,arg) \ { struct _pthread_cleanup_buffer _buffer; \ _pthread_cleanup_push (&_buffer, (routine), (arg)); #define pthread_cleanup_pop(execute) \ _pthread_cleanup_pop (&_buffer, (execute)); \ }
可見pthread_cleanup_push()帶有一個"{",而pthread_cleanup_pop()帶有一個"}",因此這兩個函數必須成對出現,且必須位於程序的同一級別的代碼段中才能通過編譯。
注意:
pthread_exit終止線程與線程直接return終止線程的區別。
pthread_cleanup_push()函數與pthread_cleanup_pop()函數必須成對的出現在同一個函數中。
查看本欄目更多精彩內容:http://www.bianceng.cn/OS/unix/
示例代碼:
#include <stdio.h> #include <pthread.h> #include <errno.h> #include <stdlib.h> void cleanup(void *arg) { printf("cleanup: %s\n", (char*) arg); } void *thr_fn1(void *arg) { printf("thread 1 start\n"); pthread_cleanup_push(cleanup, "thread 1-1 handler"); pthread_cleanup_push(cleanup, "thread 1-2 handler"); printf("thread 1 push complete\n"); if (arg) pthread_exit((void*) 1); pthread_cleanup_pop(0); pthread_cleanup_pop(1); return((void*) 10); } void *thr_fn2(void *arg) { printf("thread 2 start\n"); pthread_cleanup_push(cleanup, "thread 2-1 handler"); pthread_cleanup_push(cleanup, "thread 2-2 handler"); printf("thread 2 push complete\n"); pthread_cleanup_pop(0); // if (arg) // return ((void*) 2); pthread_cleanup_pop(1); //pthread_exit((void*) 20); return ((void*) 20); } int main(void) { int err; pthread_t tid1, tid2; void *tret; err = pthread_create(&tid1, NULL, thr_fn1, (void*)1); if (err) fprintf(stderr, "can't create thread 1: %d\n", strerror(errno)); err = pthread_create(&tid2, NULL, thr_fn2, (void*)1); if (err) fprintf(stderr, "can't create thread 2: %d\n", strerror(errno)); err = pthread_join(tid1, &tret); if (err) fprintf(stderr, "can't join with thread 1: %d\n", strerror(errno)); printf("thread 1 exit code %lu\n", (unsigned long) tret); err = pthread_join(tid2, &tret); if (err) fprintf(stderr, "can't join with thread 2: %d\n", strerror(errno)); printf("thread 2 exit code %lu\n", (unsigned long) tret); exit(0); }
運行結果:
huangcheng@ubuntu:~$ ./a.out thread 2 start thread 2 push complete cleanup: thread 2-1 handler thread 1 start thread 1 push complete cleanup: thread 1-2 handler cleanup: thread 1-1 handler thread 1 exit code 1 thread 2 exit code 20
在默認情況下,線程的終止狀態會保存到對該線程調用pthread_join,如果線程已經處於分離狀態,線程的底層存儲資源可以在線程終止時立即被收回。當線程被分離時,並不能用pthread_join等待它的終止狀態。對分離狀態的線程調用pthread_join會失敗,返回EINVAL。
pthread_detach調用可以用於使線程進入分離狀態:
#include <pthread.h> int pthread_detach(pthread_t tid);//返回值:若成功則返回0,否則返回錯誤編號
現在可以看出線程函數和進程函數之間的相似之處: