對傳統的UNIX進程來說,一個進程中只有一個線程,這就意味着一個進程在同一時刻只能作一件事(即便是多核CPU)。使用多線程技術, 咱們能夠設計程序使得一個進程在同一時刻作多件事。使用多線程編程具備如下優點:node
一個線程由一個進程中那些能表明當前執行上下文的所必要的信息組成。它包括一個用於標誌線程的線程ID、一個寄存器值的集合、一個堆棧、一個優先級表、一個信號掩碼、一個errno變量和一個指定線程數據(thread-specific data)。進程中的全部資源都被這個進程中的全部線程所共享,它包括程序執行上下文、程序的全局和堆內存、堆棧、文件描述符 。咱們接下來要引述的線程接口來自 POSIX.1-2001。編程
正如每一個進程都有一個進程ID同樣,每一個線程都有一個線程ID,只不過一個進程的進程ID在系統中全局惟一,而線程ID只在線程所屬的進程中有意義。線程ID使用數據類型pthread_t來表示。實現被容許使用結構體來表明pthread_t類型,所以好的實現不該將pthread_t當成整數來對待。所以,咱們必須使用函數來比較兩個線程ID:多線程
1 #include <pthread.h> 2 3 /* Return:nozero if equal, 0 otherwise */ 4 int pthread_equal(pthread_t tid1, pthread_t tid2);
Linux3.2.0使用用無符號長整型實現pthread_t。Solaris 10 使用無符號整形表明pthread_t。FreeBSD 8.0和Mac OS X 10.6.8 使用執行pthread結構體的指針來表明pthread_t。 異步
獲取線程ID異步編程
1 #include <pthread.h> 2 3 /* Return:the thread ID of the calling thread */ 4 pthread_t pthread_self(void);
使用pthread,在程序啓動的時候一個進程也是僅有一個線程的,在程序運行的時候他與傳統的進程沒有什麼區別, 直到他在進程中建立了更多的多線程。函數
1 #include <pthread.h> 2 3 /* Return: 0 if ok, error number on failure */ 4 int pthread_create( 5 pthread_t* restrict tidp, 6 const pthread_attr_t* restrict attr, 7 void* (* start_rtn)(void*), 8 void* restrict arg);
tidp 用於獲取線程成功建立後的線程Id;attr用戶自定各類線程屬性;start_rtn指定線程要執行的函數地址;arg爲start_rtn指向函數的參數;性能
新建立的線程能夠訪問進程地址空間並繼承調用線程的浮點環境(floating-point environment)和信號掩碼,然而新線程的阻塞信號集是被清空的。注意pthread類函數在失敗時一般返回一個錯誤碼而不像其餘POSIX函數那樣設置errno。每一個線程擁有一個errno副本僅僅是爲了與現有使用errno的函數兼容。編碼
若是一個進程中任何一個線程調用了 exit、_exit或_Exit,那麼整個進程會被停止。一樣的,向一個線程一個默認處理方式是終止進程的信號會停止這個線程所在的進程。spa
單個線程能夠有如下三種退出方式:操作系統
1 #include <pthread.h> 2 3 /* 4 終止線程並經過rval_ptr返回一個值, 5 rval_ptr能夠被同一進程中調用pthread_join 6 方法的線程獲取到 7 */ 8 void pthread_exit(void* rval_ptr); 9 10 /* 11 等待thread線程結束,thread必須是joinable的。 12 若是rval_ptr不爲空,它會複製目標線程的退出碼( 13 如目標線程在pthread_exit中提供的值)到rval_ptr 14 指向的位置。若是目標線程被取消PTHREAD_CANCELED 15 會被放置到rval_ptr指向的位置 16 */ 17 void pthread_join(pthread_t thread, void** rval_ptr);
傳遞給pthread_exit 和 pthread_create的無類型指針可用於傳輸複雜類型數據。經過pthread_jion方法咱們能夠將咱們等待的線程置於檢測狀態(detached state),而此時調用線程就能夠發現(discover)被等待線程的資源。應當注意的是,當pthread_exit調用結束時,他的rval_ptr的值還是有效的。這就意味着若是rval_ptr指向的內存在調用線程的堆棧(Stack)上分配,那麼rval_ptr在被使用的時候它指向的內存的內容可能已經改變。舉例來講,若是一個線程在它的堆棧上給一個struct結構分配了一塊內存,並將struct結構做爲參數傳遞給了pthread_exit函數,那麼當pthread_join的調用線程 在使用rval_ptr時,這個結構可能已經被銷燬而他指向的內存可能已經用於他處。爲了不這種狀況,咱們應當使用全局變量或者在堆(Heap)上給結構分配內存。
一個線程能夠經過pthread_cancel方法請求取消同一進程中另外一線程的執行:
1 #include <pthread.h> 2 3 /* Return: 0 if OK,error number on failuer */ 4 int pthread_cancel(pthread_t tid);
默認狀況下,調用pthread_cancel 函數會使tid線程的行爲就像它本身使用PTHREAD_CANCELED參數調用了pthread_exit同樣。線程能夠選擇忽略或其餘的處理方式來處理cancel請求。pthread_cancel不會等待線程結束,它幾乎只是發送cancel請求。
線程能夠安排在它退出時須要執行的函數,這些函數通常是一些線程清理句柄(thread cleanup handlers) 。一個線程能夠創建多個清理句柄,這些句柄存儲在堆棧(stack)中,即他們會按註冊時的順序逆序執行。
1 #include <pthread.h> 2 3 /* 註冊清理函數 */ 4 void pthread_cleanup_push(void (*rtn) (void* ), void* arg); 5 6 /* 移除棧頂的清理函數,若是excute不是0,將執行清理函數 */ 7 void pthread_cleanup_pop(int excute);
pthread_cleanup_push註冊的函數在如下三種狀況下會被調用:
不管pthread_cleanup_pop函數再被調用時是否使用了非0參數,他都會將棧頂的pthread_cleanup_push註冊的清理函數移除掉。注意, return並不會執行註冊的清理函數,咱們不該該在pthread_cleanup_push和pthread_cleanup_pop之間使用return,惟一可行的辦法是在他們之間調用pthread_exit。
默認狀況下,一個線程的退出狀態會一直保留,除非咱們對這個線程調用pthread_join函數(調用後線程處於detached 狀態)。若是一個線程被distach了,那麼當它退出時它的底層存儲會被當即回收;可使用thread_detach函數來detach一個線程:
1 #include <pthread.h> 2 3 /* Return: 0 if OK, error number if failure */ 4 int pthread_depatch(pthread_t tid);
線程與進程函數對照表:
進程主要函數 | 線程主要函數 | 描述 |
fork | pthread_create | 建立新的實例 |
exit | pthread_exit | 退出 |
waitpid | pthread_join | 等待實例結束並獲取結束碼 |
atexit | pthread_cleanup_push | 註冊推出前要執行的函數 |
getpid | pthread_self | 獲取實例ID |
abort | pthread_cancel | 請求終止實例 |