Posix線程編程指南(3)

這是一個關於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的條件等待及主線程的條件激發都能正常工做。
若是不作註釋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()返回後解鎖。
    
#i nclude <stdio.h>
#i nclude <pthread.h>
#i nclude <unistd.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void * child1(void *arg){
pthread_cleanup_push(pthread_mutex_unlock,&mutex); /* comment 1 */
while(1){
printf("thread 1 get running n");
printf("thread 1 pthread_mutex_lock returns %dn",pthread_mutex_lock(&mutex));
pthread_cond_wait(&cond,&mutex);
printf("thread 1 condition appliedn");
pthread_mutex_unlock(&mutex);
sleep(5);
}
pthread_cleanup_pop(0); /* comment 2 */
}
void *child2(void *arg){
while(1){
sleep(3);
/* comment 3 */
printf("thread 2 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); sleep(5); } pthread_cleanup_pop(0); /* comment 2 */ } void *child2(void *arg) { while(1){ sleep(3); /* comment 3 */ printf("thread 2 get running.\n"); printf("thread 2 pthread_mutex_lock returns %d\n", pthread_mutex_lock(&mutex)); pthread_cond_wait(&cond,&mutex); printf("thread 2 condition applied\n"); pthread_mutex_unlock(&mutex); sleep(1); } } int main(void) { int tid1,tid2; printf("hello, condition variable test\n"); pthread_mutex_init(&mutex,NULL); pthread_cond_init(&cond,NULL); pthread_create(&tid1,NULL,child1,NULL); pthread_create(&tid2,NULL,child2,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); }
#i nclude <stdio.h>
#i nclude <pthread.h>
#i nclude <unistd.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void * child1(void *arg){
pthread_cleanup_push(pthread_mutex_unlock,&mutex); /* comment 1 */
while(1){
printf("thread 1 get running n");
printf("thread 1 pthread_mutex_lock returns %dn",pthread_mutex_lock(&mutex));
pthread_cond_wait(&cond,&mutex);
printf("thread 1 condition appliedn");
pthread_mutex_unlock(&mutex);
sleep(5);
}
pthread_cleanup_pop(0); /* comment 2 */
}
void *child2(void *arg){
while(1){
sleep(3);
/* comment 3 */
printf("thread 2 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); sleep(5); } pthread_cleanup_pop(0); /* comment 2 */ } void *child2(void *arg) { while(1){ sleep(3); /* comment 3 */ printf("thread 2 get running.\n"); printf("thread 2 pthread_mutex_lock returns %d\n", pthread_mutex_lock(&mutex)); pthread_cond_wait(&cond,&mutex); printf("thread 2 condition applied\n"); pthread_mutex_unlock(&mutex); sleep(1); } } int main(void) { int tid1,tid2; printf("hello, condition variable test\n"); pthread_mutex_init(&mutex,NULL); pthread_cond_init(&cond,NULL); pthread_create(&tid1,NULL,child1,NULL); pthread_create(&tid2,NULL,child2,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); }
條件變量機制不是異步信號安全的,也就是說,在信號處理函數中調用 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的信號燈註銷函數不作其餘動做。
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。
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在覈內是做爲共享存儲區、共享文件系統屬性、共享信號處理、共享文件描述符的獨立進程看待的。

0javascript

收藏java

xunet

33篇文章,12W+人氣,0粉絲

Ctrl+Enter 發佈linux

發佈編程

取消安全

相關文章
相關標籤/搜索