天天看點

linux pthread【轉】

Posix線程程式設計指南(1)  

内容:  

一、 線程建立  

二、線程取消  

關于作者  

線程建立與取消  

楊沙洲([email protected])  

2001 年 10 月  

這是一個關于Posix線程程式設計的專欄。作者在闡明概念的基礎上,将向您詳細講述Posix線程庫API。本文是第一篇将向您講述線程的建立與取消。 

1.1 線程與程序  

相對程序而言,線程是一個更加接近于執行體的概念,它可以與同程序中的其他線程共享資料,但擁有自己的棧空間,擁有獨立的執行序列。在串行程式基礎上引入線程和程序是為了提高程式的并發度,進而提高程式運作效率和響應時間。  

線程和程序在使用上各有優缺點:線程執行開銷小,但不利于資源的管理和保護;而程序正相反。同時,線程适合于在SMP機器上運作,而程序則可以跨機器遷移。  

1.2 建立線程  

POSIX通過pthread_create()函數建立線程,API定義如下:  

int pthread_create(pthread_t * thread, pthread_attr_t * attr,  

void * (*start_routine)(void *), void * arg)  

與fork()調用建立一個程序的方法不同,pthread_create()建立的線程并不具備與主線程(即調用pthread_create()的線程)同樣的執行序列,而是使其運作start_routine(arg)函數。thread傳回建立的線程ID,而attr是建立線程時設定的線程屬性(見下)。pthread_create()的傳回值表示線程建立是否成功。盡管arg是void *類型的變量,但它同樣可以作為任意類型的參數傳給start_routine()函數;同時,start_routine()可以傳回一個void *類型的傳回值,而這個傳回值也可以是其他類型,并由pthread_join()擷取。  

1.3 線程建立屬性  

pthread_create()中的attr參數是一個結構指針,結構中的元素分别對應着新線程的運作屬性,主要包括以下幾項:  

__detachstate,表示新線程是否與程序中其他線程脫離同步,如果置位則新線程不能用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一值。  

pthread_attr_t結構中還有一些值,但不使用pthread_create()來設定。  

為了設定這些屬性,POSIX定義了一系列屬性設定函數,包括pthread_attr_init()、pthread_attr_destroy()和與各個屬性相關的pthread_attr_get---/pthread_attr_set---函數。  

1.4 線程建立的Linux實作  

我們知道,Linux的線程實作是在核外進行的,核内提供的是建立程序的接口do_fork()。核心提供了兩個系統調用__clone()和fork(),最終都用不同的參數調用do_fork()核内API。當然,要想實作線程,沒有核心對多程序(其實是輕量級程序)共享資料段的支援是不行的,是以,do_fork()提供了很多參數,包括CLONE_VM(共享記憶體空間)、CLONE_FS(共享檔案系統資訊)、CLONE_FILES(共享檔案描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享程序ID,僅對核内程序,即0号程序有效)。當使用fork系統調用時,核心調用do_fork()不使用任何共享屬性,程序擁有獨立的運作環境,而使用pthread_create()來建立線程時,則最終設定了所有這些屬性來調用__clone(),而這些參數又全部傳給核内的do_fork(),進而建立的"程序"擁有共享的運作環境,隻有棧是獨立的,由__clone()傳入。  

Linux線程在核内是以輕量級程序的形式存在的,擁有獨立的程序表項,而所有的建立、同步、删除等操作都在核外pthread庫中進行。pthread庫使用一個管理線程(__pthread_manager(),每個程序獨立且唯一)來管理線程的建立和終止,為線程配置設定線程ID,發送線程相關的信号(比如Cancel),而主線程(pthread_create())的調用者則通過管道将請求資訊傳給管理線程。  

2.1 線程取消的定義  

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

2.2 線程取消的語義  

線程取消的方法是向目标線程發Cancel信号,但如何處理Cancel信号則由目标線程自己決定,或者忽略、或者立即終止、或者繼續運作至Cancelation-point(取消點),由不同的Cancelation狀态決定。  

線程接收到CANCEL信号的預設處理(即pthread_create()建立線程的預設狀态)是繼續運作至取消點,也就是說設定一個CANCELED狀态,線程繼續運作,隻有運作至Cancelation-point的時候才會退出。  

2.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);  

2.4 程式設計方面的考慮  

如果線程處于無限循環中,且循環體内沒有執行至取消點的必然路徑,則線程無法由外部其他線程的取消請求而終止。是以在這樣的循環體的必經路徑上應該加入pthread_testcancel()調用。  

2.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狀态,如果是,則進行取消動作,否則直接傳回。 

posix線程程式設計指南(2)  

一. 概念及作用  

二. 建立和登出  

三. 通路  

四. 使用範例  

相關内容:  

(1) 線程建立與取消  

線程私有資料  

這是一個關于Posix線程程式設計的專欄。作者在闡明概念的基礎上,将向您詳細講述Posix線程庫API。本文是第二篇将向您講述線程的私有資料。  

在單線程程式中,我們經常要用到"全局變量"以實作多個函數間共享資料。在多線程環境下,由于資料空間是共享的,是以全局變量也為所有線程所共有。但有時應用程式設計中有必要提供線程私有的全局變量,僅在某個線程中有效,但卻可以跨多個函數通路,比如程式可能需要每個線程維護一個連結清單,而使用相同的函數操作,最簡單的辦法就是使用同名而不同變量位址的線程相關資料結構。這樣的資料結構可以由Posix線程庫維護,稱為線程私有資料(Thread-specific Data,或TSD)。  

Posix定義了兩個API分别用來建立和登出TSD:  

int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *))  

該函數從TSD池中配置設定一項,将其值賦給key供以後通路使用。如果destr_function不為空,線上程退出(pthread_exit())時将以key所關聯的資料為參數調用destr_function(),以釋放配置設定的緩沖區。  

不論哪個線程調用pthread_key_create(),所建立的key都是所有線程可通路的,但各個線程可根據自己的需要往key中填入不同的值,這就相當于提供了一個同名而不同值的全局變量。在LinuxThreads的實作中,TSD池用一個結構數組表示:  

static struct pthread_key_struct pthread_keys[pthread_KEYS_MAX] = { { 0, NULL } };  

建立一個TSD就相當于将結構數組中的某一項設定為"in_use",并将其索引傳回給*key,然後設定destructor函數為destr_function。  

登出一個TSD采用如下API:  

int pthread_key_delete(pthread_key_t key)  

這個函數并不檢查目前是否有線程正使用該TSD,也不會調用清理函數(destr_function),而隻是将TSD釋放以供下一次調用pthread_key_create()使用。在LinuxThreads中,它還會将與之相關的線程資料項設為NULL(見"通路")。  

TSD的讀寫都通過專門的Posix Thread函數進行,其API定義如下:  

int pthread_setspecific(pthread_key_t key, const void *pointer)  

void * pthread_getspecific(pthread_key_t key)  

寫入(pthread_setspecific())時,将pointer的值(不是所指的内容)與key相關聯,而相應的讀出函數則将與key相關聯的資料讀出來。資料類型都設為void *,是以可以指向任何類型的資料。  

在LinuxThreads中,使用了一個位于線程描述結構(_pthread_descr_struct)中的二維void *指針數組來存放與key關聯的資料,數組大小由以下幾個宏來說明: #define pthread_KEY_2NDLEVEL_SIZE 32  

#define pthread_KEY_1STLEVEL_SIZE \  

((pthread_KEYS_MAX + pthread_KEY_2NDLEVEL_SIZE - 1)  

/ pthread_KEY_2NDLEVEL_SIZE)  

其中在/usr/include/bits/local_lim.h中定義了pthread_KEYS_MAX為1024,是以一維數組大小為32。而具體存放的位置由key值經過以下計算得到:  

idx1st = key / pthread_KEY_2NDLEVEL_SIZE  

idx2nd = key % pthread_KEY_2NDLEVEL_SIZE  

也就是說,資料存放與一個32×32的稀疏矩陣中。同樣,通路的時候也由key值經過類似計算得到資料所在位置索引,再取出其中内容傳回。  

以下這個例子沒有什麼實際意義,隻是說明如何使用,以及能夠使用這一機制達到存儲線程私有資料的目的。  

#include <stdio.h>  

#include <pthread.h>  

pthread_key_t key;  

void echomsg(int t)  

{  

printf("destructor excuted in thread %d,param=%d\n",pthread_self(),t);  

}  

void * child1(void *arg)  

int tid=pthread_self();  

printf("thread %d enter\n",tid);  

pthread_setspecific(key,(void *)tid);  

sleep(2);  

printf("thread %d returns %d\n",tid,pthread_getspecific(key));  

sleep(5);  

void * child2(void *arg)  

sleep(1);  

int main(void)  

int tid1,tid2;  

printf("hello\n");  

pthread_key_create(&key,echomsg);  

pthread_create(&tid1,NULL,child1,NULL);  

pthread_create(&tid2,NULL,child2,NULL);  

sleep(10);  

pthread_key_delete(key);  

printf("main thread exit\n");  

return 0;  

給例程建立兩個線程分别設定同一個線程私有資料為自己的線程ID,為了檢驗其私有性,程式錯開了兩個線程私有資料的寫入和讀出的時間,從程式運作結果可以看出,兩個線程對TSD的修改互不幹擾。同時,當線程退出時,清理函數會自動執行,參數為tid。 

Posix線程程式設計指南(3)  

一. 互斥鎖  

二. 條件變量  

三. 信号燈  

四. 異步信号  

五. 其他同步方式  

(2) 線程私有資料  

線程同步  

這是一個關于Posix線程程式設計的專欄。作者在闡明概念的基礎上,将向您詳細講述Posix線程庫API。本文是第三篇将向您講述線程同步。  

盡管在Posix Thread中同樣可以使用IPC的信号量機制來實作互斥鎖mutex功能,但顯然semphore的功能過于強大了,在Posix Thread中定義了另外一套專門用于線程同步的mutex函數。  

1. 建立和銷毀  

有兩種方法建立互斥鎖,靜态方式和動态方式。POSIX定義了一個宏pthread_MUTEX_INITIALIZER來靜态初始化互斥鎖,方法如下:  

pthread_mutex_t mutex=pthread_MUTEX_INITIALIZER;  

在LinuxThreads實作中,pthread_mutex_t是一個結構,而pthread_MUTEX_INITIALIZER則是一個結構常量。  

動态方式是采用pthread_mutex_init()函數來初始化互斥鎖,API定義如下:  

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)  

其中mutexattr用于指定互斥鎖屬性(見下),如果為NULL則使用預設屬性。  

pthread_mutex_destroy()用于登出一個互斥鎖,API定義如下:  

int pthread_mutex_destroy(pthread_mutex_t *mutex)  

銷毀一個互斥鎖即意味着釋放它所占用的資源,且要求鎖目前處于開放狀态。由于在Linux中,互斥鎖并不占用任何資源,是以LinuxThreads中的pthread_mutex_destroy()除了檢查鎖狀态以外(鎖定狀态則傳回EBUSY)沒有其他動作。  

2. 互斥鎖屬性  

互斥鎖的屬性在建立鎖的時候指定,在LinuxThreads實作中僅有一個鎖類型屬性,不同的鎖類型在試圖對一個已經被鎖定的互斥鎖加鎖時表現不同。目前(glibc2.2.3,linuxthreads0.9)有四個值可供選擇:  

pthread_MUTEX_TIMED_NP,這是預設值,也就是普通鎖。當一個線程加鎖以後,其餘請求鎖的線程将形成一個等待隊列,并在解鎖後按優先級獲得鎖。這種鎖政策保證了資源配置設定的公平性。  

pthread_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個線程對同一個鎖成功獲得多次,并通過多次unlock解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新競争。  

pthread_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個線程請求同一個鎖,則傳回EDEADLK,否則與pthread_MUTEX_TIMED_NP類型動作相同。這樣就保證當不允許多次加鎖時不會出現最簡單情況下的死鎖。  

pthread_MUTEX_ADAPTIVE_NP,适應鎖,動作最簡單的鎖類型,僅等待解鎖後重新競争。  

3. 鎖操作  

鎖操作主要包括加鎖pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測試加鎖pthread_mutex_trylock()三個,不論哪種類型的鎖,都不可能被兩個不同的線程同時得到,而必須等待解鎖。對于普通鎖和适應鎖類型,解鎖者可以是同程序内任何線程;而檢錯鎖則必須由加鎖者解鎖才有效,否則傳回EPERM;對于嵌套鎖,文檔和實作要求必須由加鎖者解鎖,但實驗結果表明并沒有這種限制,這個不同目前還沒有得到解釋。在同一程序中的線程,如果加鎖後沒有解鎖,則任何其他線程都無法再獲得鎖。  

int pthread_mutex_lock(pthread_mutex_t *mutex)  

int pthread_mutex_unlock(pthread_mutex_t *mutex)  

int pthread_mutex_trylock(pthread_mutex_t *mutex)  

pthread_mutex_trylock()語義與pthread_mutex_lock()類似,不同的是在鎖已經被占據時傳回EBUSY而不是挂起等待。  

4. 其他  

POSIX線程鎖機制的Linux實作都不是取消點,是以,延遲取消類型的線程不會因收到取消信号而離開加鎖等待。值得注意的是,如果線程在加鎖後解鎖前被取消,鎖将永遠保持鎖定狀态,是以如果在關鍵區段内有取消點存在,或者設定了異步取消類型,則必須在退出回調函數中解鎖。  

這個鎖機制同時也不是異步信号安全的,也就是說,不應該在信号處理過程中使用互斥鎖,否則容易造成死鎖。  

條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:一個線程等待"條件變量的條件成立"而挂起;另一個線程使"條件成立"(給出條件成立信号)。為了防止競争,條件變量的使用總是和一個互斥鎖結合在一起。  

1. 建立和登出  

條件變量和互斥鎖一樣,都有靜态動态兩種建立方式,靜态方式使用pthread_COND_INITIALIZER常量,如下:  

pthread_cond_t cond=pthread_COND_INITIALIZER  

動态方式調用pthread_cond_init()函數,API定義如下:  

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)  

盡管POSIX标準中為條件變量定義了屬性,但在LinuxThreads中沒有實作,是以cond_attr值通常為NULL,且被忽略。  

登出一個條件變量需要調用pthread_cond_destroy(),隻有在沒有線程在該條件變量上等待的時候才能登出這個條件變量,否則傳回EBUSY。因為Linux實作的條件變量沒有配置設定什麼資源,是以登出動作隻包括檢查是否有等待線程。API定義如下:  

int pthread_cond_destroy(pthread_cond_t *cond)  

2. 等待和激發  

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)  

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)  

等待條件有兩種方式:無條件等待pthread_cond_wait()和計時等待pthread_cond_timedwait(),其中計時等待方式如果在給定時刻前條件沒有滿足,則傳回ETIMEOUT,結束等待,其中abstime以與time()系統調用相同意義的絕對時間形式出現,0表示格林尼治時間1970年1月1日0時0分0秒。 

無論哪種等待方式,都必須和一個互斥鎖配合,以防止多個線程同時請求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的競争條件(Race Condition)。mutex互斥鎖必須是普通鎖(pthread_MUTEX_TIMED_NP)或者适應鎖(pthread_MUTEX_ADAPTIVE_NP),且在調用pthread_cond_wait()前必須由本線程加鎖(pthread_mutex_lock()),而在更新條件等待隊列以前,mutex保持鎖定狀态,并線上程挂起進入等待前解鎖。在條件滿足進而離開pthread_cond_wait()之前,mutex将被重新加鎖,以與進入pthread_cond_wait()前的加鎖動作對應。  

激發條件有兩種形式,pthread_cond_signal()激活一個等待該條件的線程,存在多個等待線程時按入隊順序激活其中一個;而pthread_cond_broadcast()則激活所有等待線程。  

3. 其他  

pthread_cond_wait()和pthread_cond_timedwait()都被實作為取消點,是以,在該處等待的線程将立即重新運作,在重新鎖定mutex後離開pthread_cond_wait(),然後執行取消動作。也就是說如果pthread_cond_wait()被取消,mutex是保持鎖定狀态的,因而需要定義退出回調函數來為其解鎖。  

以下示例集中示範了互斥鎖和條件變量的結合使用,以及取消對于條件等待動作的影響。在例子中,有兩個線程被啟動,并等待同一個條件變量,如果不使用退出回調函數(見範例中的注釋部分),則tid2将在pthread_mutex_lock()處永久等待。如果使用回調函數,則tid2的條件等待及主線程的條件激發都能正常工作。  

#include <unistd.h>  

pthread_mutex_t mutex;  

pthread_cond_t cond;  

pthread_cleanup_push(pthread_mutex_unlock,&mutex); /* comment 1 */  

while(1){  

printf("thread 1 get running \n");  

printf("thread 1 pthread_mutex_lock returns %d\n",  

pthread_mutex_lock(&mutex));  

pthread_cond_wait(&cond,&mutex);  

printf("thread 1 condition applied\n");  

pthread_mutex_unlock(&mutex);  

pthread_cleanup_pop(0); /* comment 2 */  

void *child2(void *arg)  

sleep(3); /* comment 3 */  

printf("thread 2 get running.\n");  

printf("thread 2 pthread_mutex_lock returns %d\n",  

printf("thread 2 condition applied\n");  

printf("hello, condition variable test\n");  

pthread_mutex_init(&mutex,NULL);  

pthread_cond_init(&cond,NULL);  

do{  

sleep(2); /* comment 4 */  

pthread_cancel(tid1); /* comment 5 */  

sleep(2); /* comment 6 */  

pthread_cond_signal(&cond);  

}while(1);  

sleep(100);  

pthread_exit(0);  

如果不做注釋5的pthread_cancel()動作,即使沒有那些sleep()延時操作,child1和child2都能正常工作。注釋3和注釋4的延遲使得child1有時間完成取消動作,進而使child2能在child1退出之後進入請求鎖操作。如果沒有注釋1和注釋2的回調函數定義,系統将挂起在child2請求鎖的地方;而如果同時也不做注釋3和注釋4的延時,child2能在child1完成取消動作以前得到控制,進而順利執行申請鎖的操作,但卻可能挂起在pthread_cond_wait()中,因為其中也有申請mutex的操作。child1函數給出的是标準的條件變量的使用方式:回調函數保護,等待條件前鎖定,pthread_cond_wait()傳回後解鎖。  

條件變量機制不是異步信号安全的,也就是說,在信号處理函數中調用pthread_cond_signal()或者pthread_cond_broadcast()很可能引起死鎖。  

信号燈與互斥鎖和條件變量的主要不同在于"燈"的概念,燈亮則意味着資源可用,燈滅則意味着不可用。如果說後兩中同步方式側重于"等待"操作,即資源不可用的話,信号燈機制則側重于點燈,即告知資源可用;沒有等待線程的解鎖或激發條件都是沒有意義的,而沒有等待燈亮的線程的點燈操作則有效,且能保持燈亮狀态。當然,這樣的操作原語也意味着更多的開銷。  

信号燈的應用除了燈亮/燈滅這種二進制燈以外,也可以采用大于1的燈數,以表示資源數大于1,這時可以稱之為多元燈。  

POSIX信号燈标準定義了有名信号燈和無名信号燈兩種,但LinuxThreads的實作僅有無名燈,同時有名燈除了總是可用于多程序之間以外,在使用上與無名燈并沒有很大的差別,是以下面僅就無名燈進行讨論。  

int sem_init(sem_t *sem, int pshared, unsigned int value)  

這是建立信号燈的API,其中value為信号燈的初值,pshared表示是否為多程序共享而不僅僅是用于一個程序。LinuxThreads沒有實作多程序共享信号燈,是以所有非0值的pshared輸入都将使sem_init()傳回-1,且置errno為ENOSYS。初始化好的信号燈由sem變量表征,用于以下點燈、滅燈操作。  

int sem_destroy(sem_t * sem)  

被登出的信号燈sem要求已沒有線程在等待該信号燈,否則傳回-1,且置errno為EBUSY。除此之外,LinuxThreads的信号燈登出函數不做其他動作。  

2. 點燈和滅燈  

int sem_post(sem_t * sem)  

點燈操作将信号燈值原子地加1,表示增加一個可通路的資源。  

int sem_wait(sem_t * sem)  

int sem_trywait(sem_t * sem)  

sem_wait()為等待燈亮操作,等待燈亮(信号燈值大于0),然後将信号燈原子地減1,并傳回。sem_trywait()為sem_wait()的非阻塞版,如果信号燈計數大于0,則原子地減1并傳回0,否則立即傳回-1,errno置為EAGAIN。  

3. 擷取燈值  

int sem_getvalue(sem_t * sem, int * sval)  

讀取sem中的燈計數,存于*sval中,并傳回0。  

sem_wait()被實作為取消點,而且在支援原子"比較且交換"指令的體系結構上,sem_post()是唯一能用于異步信号處理函數的POSIX異步信号安全的API。  

由于LinuxThreads是在核外使用核内輕量級程序實作的線程,是以基于核心的異步信号操作對于線程也是有效的。但同時,由于異步信号總是實際發往某個程序,是以無法實作POSIX标準所要求的"信号到達某個程序,然後再由該程序将信号分發到所有沒有阻塞該信号的線程中"原語,而是隻能影響到其中一個線程。  

POSIX異步信号同時也是一個标準C庫提供的功能,主要包括信号集管理(sigemptyset()、sigfillset()、sigaddset()、sigdelset()、sigismember()等)、信号處理函數安裝(sigaction())、信号阻塞控制(sigprocmask())、被阻塞信号查詢(sigpending())、信号等待(sigsuspend())等,它們與發送信号的kill()等函數配合就能實作程序間異步信号功能。LinuxThreads圍繞線程封裝了sigaction()何raise(),本節集中讨論LinuxThreads中擴充的異步信号函數,包括pthread_sigmask()、pthread_kill()和sigwait()三個函數。毫無疑問,所有POSIX異步信号函數對于線程都是可用的。  

int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask)  

設定線程的信号屏蔽碼,語義與sigprocmask()相同,但對不允許屏蔽的Cancel信号和不允許響應的Restart信号進行了保護。被屏蔽的信号儲存在信号隊列中,可由sigpending()函數取出。  

int pthread_kill(pthread_t thread, int signo)  

向thread号線程發送signo信号。實作中在通過thread線程号定位到對應程序号以後使用kill()系統調用完成發送。  

int sigwait(const sigset_t *set, int *sig)  

挂起線程,等待set中指定的信号之一到達,并将到達的信号存入*sig中。POSIX标準建議在調用sigwait()等待信号以前,程序中所有線程都應屏蔽該信号,以保證僅有sigwait()的調用者獲得該信号,是以,對于需要等待同步的異步信号,總是應該在建立任何線程以前調用pthread_sigmask()屏蔽該信号的處理。而且,調用sigwait()期間,原來附接在該信号上的信号處理函數不會被調用。  

如果在等待期間接收到Cancel信号,則立即退出等待,也就是說sigwait()被實作為取消點。  

除了上述讨論的同步方式以外,其他很多程序間通信手段對于LinuxThreads也是可用的,比如基于檔案系統的IPC(管道、Unix域Socket等)、消息隊列(Sys.V或者Posix的)、System V的信号燈等。隻有一點需要注意,LinuxThreads在核内是作為共享存儲區、共享檔案系統屬性、共享信号處理、共享檔案描述符的獨立程序看待的。 

Posix線程程式設計指南(4)  

1. 線程終止方式  

2. 線程終止時的清理  

3. 線程終止的同步及其傳回值  

4. 關于pthread_exit()和return  

參考資料  

(3) 線程同步  

線程終止  

2001 年 11 月  

這是一個關于Posix線程程式設計的專欄。作者在闡明概念的基礎上,将向您詳細講述Posix線程庫API。本文是第四篇将向您講述線程中止。  

一般來說,Posix的線程終止有兩種情況:正常終止和非正常終止。線程主動調用pthread_exit()或者從線程函數中return都将使線程正常退出,這是可預見的退出方式;非正常終止是線程在其他線程的幹預下,或者由于自身運作出錯(比如通路非法位址)而退出,這種退出方式是不可預見的。  

不論是可預見的線程終止還是異常終止,都會存在資源釋放的問題,在不考慮因運作出錯而退出的前提下,如何保證線程終止時能順利的釋放掉自己所占用的資源,特别是鎖資源,就是一個必須考慮解決的問題。  

最經常出現的情形是資源獨占鎖的使用:線程為了通路臨界資源而為其加上鎖,但在通路過程中被外界取消,如果線程處于響應取消狀态,且采用異步方式響應,或者在打開獨占鎖以前的運作路徑上存在取消點,則該臨界資源将永遠處于鎖定狀态得不到釋放。外界取消操作是不可預見的,是以的确需要一個機制來簡化用于資源釋放的程式設計。  

在POSIX線程API中提供了一個pthread_cleanup_push()/pthread_cleanup_pop()函數對用于自動釋放資源--從pthread_cleanup_push()的調用點到pthread_cleanup_pop()之間的程式段中的終止動作(包括調用pthread_exit()和取消點終止)都将執行pthread_cleanup_push()所指定的清理函數。API定義如下:  

void pthread_cleanup_push(void (*routine) (void *), void *arg)  

void pthread_cleanup_pop(int execute)  

pthread_cleanup_push()/pthread_cleanup_pop()采用先入後出的棧結構管理,void routine(void *arg)函數在調用pthread_cleanup_push()時壓入清理函數棧,多次對pthread_cleanup_push()的調用将在清理函數棧中形成一個函數鍊,在執行該函數鍊時按照壓棧的相反順序彈出。execute參數表示執行到pthread_cleanup_pop()時是否在彈出清理函數的同時執行該函數,為0表示不執行,非0為執行;這個參數并不影響異常終止時清理函數的執行。  

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()帶有一個"}",是以這兩個函數必須成對出現,且必須位于程式的同一級别的代碼段中才能通過編譯。在下面的例子裡,當線程在"do some work"中終止時,将主動調用pthread_mutex_unlock(mut),以完成解鎖動作。  

pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);  

pthread_mutex_lock(&mut);  

/* do some work */  

pthread_mutex_unlock(&mut);  

pthread_cleanup_pop(0);  

必須要注意的是,如果線程處于pthread_CANCEL_ASYNCHRONOUS狀态,上述代碼段就有可能出錯,因為CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之間發生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之間發生,進而導緻清理函數unlock一個并沒有加鎖的mutex變量,造成錯誤。是以,在使用清理函數的時候,都應該暫時設定成pthread_CANCEL_DEFERRED模式。為此,POSIX的Linux實作中還提供了一對不保證可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()擴充函數,功能與以下代碼段相當:  

{ int oldtype;  

pthread_setcanceltype(pthread_CANCEL_DEFERRED, &oldtype);  

pthread_cleanup_push(routine, arg);  

...  

pthread_cleanup_pop(execute);  

pthread_setcanceltype(oldtype, NULL);  

一般情況下,程序中各個線程的運作都是互相獨立的,線程的終止并不會通知,也不會影響其他線程,終止的線程所占用的資源也并不會随着線程的終止而得到釋放。正如程序之間可以用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()來回收。  

理論上說,pthread_exit()和線程宿體函數退出的功能是相同的,函數結束時會在内部自動調用pthread_exit()來清理線程相關的資源。但實際上二者由于編譯器的處理有很大的不同。  

在程序主函數(main())中調用pthread_exit(),隻會使主函數所在的線程(可以說是程序的主線程)退出;而如果是return,編譯器将使其調用程序退出的代碼(如_exit()),進而導緻程序及其所有線程結束運作。  

其次,線上程宿主函數中主動調用return,如果return語句包含在pthread_cleanup_push()/pthread_cleanup_pop()對中,則不會引起清理函數的執行,反而會導緻segment fault。 

2005-9-30 14:43 回複  

wangpengyun 

6位粉絲 

10樓 

Posix線程程式設計指南(5) 

1.獲得本線程ID  

2.判斷兩個線程是否為同一線程  

3.僅執行一次的操作  

4.pthread_kill_other_threads_np()  

(4) 線程終止  

雜項  

這是一個關于Posix線程程式設計的專欄。作者在闡明概念的基礎上,将向您詳細講述Posix線程庫API。本文是第五篇将向您講述pthread_self()、pthread_equal()和pthread_once()等雜項函數。  

在Posix線程規範中還有幾個輔助函數難以歸類,暫且稱其為雜項函數,主要包括pthread_self()、pthread_equal()和pthread_once()三個,另外還有一個LinuxThreads非可移植性擴充函數pthread_kill_other_threads_np()。本文就介紹這幾個函數的定義和使用。  

1. 獲得本線程ID  

pthread_t pthread_self(void)  

本函數傳回本線程的辨別符。  

在LinuxThreads中,每個線程都用一個pthread_descr結構來描述,其中包含了線程狀态、線程ID等所有需要的資料結構,此函數的實作就是線上程棧幀中找到本線程的pthread_descr結構,然後傳回其中的p_tid項。  

pthread_t類型在LinuxThreads中定義為無符号長整型。  

2. 判斷兩個線程是否為同一線程  

int pthread_equal(pthread_t thread1, pthread_t thread2)  

判斷兩個線程描述符是否指向同一線程。在LinuxThreads中,線程ID相同的線程必然是同一個線程,是以,這個函數的實作僅僅判斷thread1和thread2是否相等。  

3. 僅執行一次的操作  

int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))  

本函數使用初值為pthread_ONCE_INIT的once_control變量保證init_routine()函數在本程序執行序列中僅執行一次。  

pthread_once_t once=pthread_ONCE_INIT;  

void once_run(void)  

printf("once_run in thread %d\n",pthread_self());  

pthread_once(&once,once_run);  

printf("thread %d returns\n",tid);  

once_run()函數僅執行一次,且究竟在哪個線程中執行是不定的,盡管pthread_once(&once,once_run)出現在兩個線程中。  

LinuxThreads使用互斥鎖和條件變量保證由pthread_once()指定的函數執行且僅執行一次,而once_control則表征是否執行過。如果once_control的初值不是pthread_ONCE_INIT(LinuxThreads定義為0),pthread_once()的行為就會不正常。在LinuxThreads中,實際"一次性函數"的執行狀态有三種:NEVER(0)、IN_PROGRESS(1)、DONE(2),如果once初值設為1,則由于所有pthread_once()都必須等待其中一個激發"已執行一次"信号,是以所有pthread_once()都會陷入永久的等待中;如果設為2,則表示該函數已執行過一次,進而所有pthread_once()都會立即傳回0。  

4. pthread_kill_other_threads_np()  

void pthread_kill_other_threads_np(void)  

這個函數是LinuxThreads針對本身無法實作的POSIX約定而做的擴充。POSIX要求當程序的某一個線程執行exec*系統調用在程序空間中加載另一個程式時,目前程序的所有線程都應終止。由于LinuxThreads的局限性,該機制無法在exec中實作,是以要求線程執行exec前手工終止其他所有線程。pthread_kill_other_threads_np()的作用就是這個。  

需要注意的是,pthread_kill_other_threads_np()并沒有通過pthread_cancel()來終止線程,而是直接向管理線程發"程序退出"信号,使所有其他線程都結束運作,而不經過Cancel動作,當然也不會執行退出回調函數。盡管LinuxThreads的實驗結果與文檔說明相同,但代碼實作中卻是用的__pthread_sig_cancel信号來kill線程,應該效果與執行pthread_cancel()是一樣的,其中原因目前還不清楚。

本文轉自張昺華-sky部落格園部落格,原文連結:http://www.cnblogs.com/sky-heaven/p/7613036.html,如需轉載請自行聯系原作者

繼續閱讀