- 論壇徽章:
- 0
|
Posix線程編程指南(3)線程同步
![]()
![]()
![]()
![]()
文檔選項(xiàng)
![]()
![]()
未顯示需要 JavaScript
的文檔選項(xiàng)
![]()
![]()
打印本頁
![]()
![]()
將此頁作為電子郵件發(fā)送
級(jí)別: 初級(jí)
楊沙洲
(
[email=pubb@163.net?subject=Posix%E7%BA%BF%E7%A8%8B%E7%BC%96%E7%A8%8B%E6%8C%87%E5%8D%97%283%29&cc=pubb@163.net]pubb@163.net[/email]
), 工程師, 自由撰稿人
2001 年 10 月 01 日
這是一個(gè)關(guān)于Posix線程編程的專欄。作者在闡明概念的基礎(chǔ)上,將向您詳細(xì)講述Posix線程庫API。本文是第三篇將向您講述線程同步。
互斥鎖
盡管在Posix
Thread中同樣可以使用IPC的信號(hào)量機(jī)制來實(shí)現(xiàn)互斥鎖mutex功能,但顯然semphore的功能過于強(qiáng)大了,在Posix
Thread中定義了另外一套專門用于線程同步的mutex函數(shù)。
1. 創(chuàng)建和銷毀
有兩種方法創(chuàng)建互斥鎖,靜態(tài)方式和動(dòng)態(tài)方式。POSIX定義了一個(gè)宏P(guān)THREAD_MUTEX_INITIALIZER來靜態(tài)初始化互斥鎖,方法如下:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
在LinuxThreads實(shí)現(xiàn)中,pthread_mutex_t是一個(gè)結(jié)構(gòu),而PTHREAD_MUTEX_INITIALIZER則是一個(gè)結(jié)構(gòu)常量。
動(dòng)態(tài)方式是采用pthread_mutex_init()函數(shù)來初始化互斥鎖,API定義如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const
pthread_mutexattr_t *mutexattr)
其中mutexattr用于指定互斥鎖屬性(見下),如果為NULL則使用缺省屬性。
pthread_mutex_destroy()用于注銷一個(gè)互斥鎖,API定義如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
銷毀一個(gè)互斥鎖即意味著釋放它所占用的資源,且要求鎖當(dāng)前處于開放狀態(tài)。由于在Linux中,互斥鎖并不占用任何資源,因此LinuxThreads中的pthread_mutex_destroy()除了檢查鎖狀態(tài)以外(鎖定狀態(tài)則返回EBUSY)沒有其他動(dòng)作。
2. 互斥鎖屬性
互斥鎖的屬性在創(chuàng)建鎖的時(shí)候指定,在LinuxThreads實(shí)現(xiàn)中僅有一個(gè)鎖類型屬性,不同的鎖類型在試圖對(duì)一個(gè)已經(jīng)被鎖定的互斥鎖加鎖時(shí)表現(xiàn)不同。當(dāng)前(glibc2.2.3,linuxthreads0.9)有四個(gè)值可供選擇:
PTHREAD_MUTEX_TIMED_NP,這是缺省值,也就是普通鎖。當(dāng)一個(gè)線程加鎖以后,其余請(qǐng)求鎖的線程將形成一個(gè)等待隊(duì)列,并在解鎖后按優(yōu)先級(jí)獲得鎖。這種鎖策略保證了資源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個(gè)線程對(duì)同一個(gè)鎖成功獲得多次,并通過多次unlock解鎖。如果是不同線程請(qǐng)求,則在加鎖線程解鎖時(shí)重新競(jìng)爭(zhēng)。
PTHREAD_MUTEX_ERRORCHECK_NP,檢錯(cuò)鎖,如果同一個(gè)線程請(qǐng)求同一個(gè)鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP類型動(dòng)作相同。這樣就保證當(dāng)不允許多次加鎖時(shí)不會(huì)出現(xiàn)最簡(jiǎn)單情況下的死鎖。
PTHREAD_MUTEX_ADAPTIVE_NP,適應(yīng)鎖,動(dòng)作最簡(jiǎn)單的鎖類型,僅等待解鎖后重新競(jìng)爭(zhēng)。
3. 鎖操作
鎖
操作主要包括加鎖pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測(cè)試加鎖
pthread_mutex_trylock()三個(gè),不論哪種類型的鎖,都不可能被兩個(gè)不同的線程同時(shí)得到,而必須等待解鎖。對(duì)于普通鎖和適應(yīng)鎖類型,
解鎖者可以是同進(jìn)程內(nèi)任何線程;而檢錯(cuò)鎖則必須由加鎖者解鎖才有效,否則返回EPERM;對(duì)于嵌套鎖,文檔和實(shí)現(xiàn)要求必須由加鎖者解鎖,但實(shí)驗(yàn)結(jié)果表明并
沒有這種限制,這個(gè)不同目前還沒有得到解釋。在同一進(jìn)程中的線程,如果加鎖后沒有解鎖,則任何其他線程都無法再獲得鎖。
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()類似,不同的是在鎖已經(jīng)被占據(jù)時(shí)返回EBUSY而不是掛起等待。
4. 其他
POSIX
線程鎖機(jī)制的Linux實(shí)現(xiàn)都不是取消點(diǎn),因此,延遲取消類型的線程不會(huì)因收到取消信號(hào)而離開加鎖等待。值得注意的是,如果線程在加鎖后解鎖前被取消,鎖
將永遠(yuǎn)保持鎖定狀態(tài),因此如果在關(guān)鍵區(qū)段內(nèi)有取消點(diǎn)存在,或者設(shè)置了異步取消類型,則必須在退出回調(diào)函數(shù)中解鎖。
這個(gè)鎖機(jī)制同時(shí)也不是異步信號(hào)安全的,也就是說,不應(yīng)該在信號(hào)處理過程中使用互斥鎖,否則容易造成死鎖。
![]()
![]()
![]()
![]()
回頁首
條件變量
條件變量是利用線程間共享的全局變量進(jìn)行同步的一種機(jī)制,主要包括兩個(gè)動(dòng)作:一個(gè)線程等待"條件變量的條件成立"而掛起;另一個(gè)線程使"條件成立"(給出條件成立信號(hào))。為了防止競(jìng)爭(zhēng),條件變量的使用總是和一個(gè)互斥鎖結(jié)合在一起。
1. 創(chuàng)建和注銷
條件變量和互斥鎖一樣,都有靜態(tài)動(dòng)態(tài)兩種創(chuàng)建方式,靜態(tài)方式使用PTHREAD_COND_INITIALIZER常量,如下:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
動(dòng)態(tài)方式調(diào)用pthread_cond_init()函數(shù),API定義如下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t
*cond_attr)
盡管POSIX標(biāo)準(zhǔn)中為條件變量定義了屬性,但在LinuxThreads中沒有實(shí)現(xiàn),因此cond_attr值通常為NULL,且被忽略。
注銷一個(gè)條件變量需要調(diào)用pthread_cond_destroy(),只有在沒有線程在該條件變量上等待的時(shí)候才能注銷這個(gè)條件變量,否則返回EBUSY。因?yàn)長(zhǎng)inux實(shí)現(xiàn)的條件變量沒有分配什么資源,所以注銷動(dòng)作只包括檢查是否有等待線程。API定義如下:
int pthread_cond_destroy(pthread_cond_t *cond)
2. 等待和激發(fā)
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()和計(jì)時(shí)等待pthread_cond_timedwait(),其中計(jì)時(shí)等待方式
如果在給定時(shí)刻前條件沒有滿足,則返回ETIMEOUT,結(jié)束等待,其中abstime以與time()系統(tǒng)調(diào)用相同意義的絕對(duì)時(shí)間形式出現(xiàn),0表示格林
尼治時(shí)間1970年1月1日0時(shí)0分0秒。
無論哪種等待方式,都必須和一個(gè)互斥鎖配合,以防止多個(gè)線程同時(shí)請(qǐng)求pthread_cond_wait()(或
pthread_cond_timedwait(),下同)的競(jìng)爭(zhēng)條件(Race
Condition)。mutex互斥鎖必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)或者適應(yīng)鎖
(PTHREAD_MUTEX_ADAPTIVE_NP),且在調(diào)用pthread_cond_wait()前必須由本線程加鎖
(pthread_mutex_lock()),而在更新條件等待隊(duì)列以前,mutex保持鎖定狀態(tài),并在線程掛起進(jìn)入等待前解鎖。在條件滿足從而離開
pthread_cond_wait()之前,mutex將被重新加鎖,以與進(jìn)入pthread_cond_wait()前的加鎖動(dòng)作對(duì)應(yīng)。
激發(fā)條件有兩種形式,pthread_cond_signal()激活一個(gè)等待該條件的線程,存在多個(gè)等待線程時(shí)按入隊(duì)順序激活其中一個(gè);而pthread_cond_broadcast()則激活所有等待線程。
3. 其他
pthread_cond_wait()
和pthread_cond_timedwait()都被實(shí)現(xiàn)為取消點(diǎn),因此,在該處等待的線程將立即重新運(yùn)行,在重新鎖定mutex后離開
pthread_cond_wait(),然后執(zhí)行取消動(dòng)作。也就是說如果pthread_cond_wait()被取消,mutex是保持鎖定狀態(tài)的,
因而需要定義退出回調(diào)函數(shù)來為其解鎖。
以下示例集中演示了互斥鎖和條件變量的結(jié)合使用,以及取消對(duì)于條件等待動(dòng)作的影響。在例子中,有兩個(gè)線程被啟動(dòng),并等待同一個(gè)條件變量,如果不使用退出回
調(diào)函數(shù)(見范例中的注釋部分),則tid2將在pthread_mutex_lock()處永久等待。如果使用回調(diào)函數(shù),則tid2的條件等待及主線程的
條件激發(fā)都能正常工作。
#include
#include
#include
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 %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);
}
如果不做注釋5的pthread_cancel()動(dòng)作,即使沒有那些sleep()延時(shí)操作,child1和child2都能正常工作。注釋3和注釋4
的延遲使得child1有時(shí)間完成取消動(dòng)作,從而使child2能在child1退出之后進(jìn)入請(qǐng)求鎖操作。如果沒有注釋1和注釋2的回調(diào)函數(shù)定義,系統(tǒng)將
掛起在child2請(qǐng)求鎖的地方;而如果同時(shí)也不做注釋3和注釋4的延時(shí),child2能在child1完成取消動(dòng)作以前得到控制,從而順利執(zhí)行申請(qǐng)鎖的
操作,但卻可能掛起在pthread_cond_wait()中,因?yàn)槠渲幸灿猩暾?qǐng)mutex的操作。child1函數(shù)給出的是標(biāo)準(zhǔn)的條件變量的使用方
式:回調(diào)函數(shù)保護(hù),等待條件前鎖定,pthread_cond_wait()返回后解鎖。
條件變量機(jī)制不是異步信號(hào)安全的,也就是說,在信號(hào)處理函數(shù)中調(diào)用pthread_cond_signal()或者pthread_cond_broadcast()很可能引起死鎖。
![]()
![]()
![]()
![]()
回頁首
信號(hào)燈
信號(hào)燈與互斥鎖和條件變量的主要不同在于"燈"的概念,燈亮則意味著資源可用,燈滅則意味著不可用。如果說后兩中同步方式側(cè)重于"等待"操作,即資源不可
用的話,信號(hào)燈機(jī)制則側(cè)重于點(diǎn)燈,即告知資源可用;沒有等待線程的解鎖或激發(fā)條件都是沒有意義的,而沒有等待燈亮的線程的點(diǎn)燈操作則有效,且能保持燈亮狀
態(tài)。當(dāng)然,這樣的操作原語也意味著更多的開銷。
信號(hào)燈的應(yīng)用除了燈亮/燈滅這種二元燈以外,也可以采用大于1的燈數(shù),以表示資源數(shù)大于1,這時(shí)可以稱之為多元燈。
1. 創(chuàng)建和注銷
POSIX信號(hào)燈標(biāo)準(zhǔn)定義了有名信號(hào)燈和無名信號(hào)燈兩種,但LinuxThreads的實(shí)現(xiàn)僅有無名燈,同時(shí)有名燈除了總是可用于多進(jìn)程之間以外,在使用上與無名燈并沒有很大的區(qū)別,因此下面僅就無名燈進(jìn)行討論。
int sem_init(sem_t *sem, int pshared, unsigned int value)
這是創(chuàng)建信號(hào)燈的API,其中value為信號(hào)燈的初值,pshared表示是否為多進(jìn)程共享而不僅僅是用于一個(gè)進(jìn)程。LinuxThreads沒有實(shí)現(xiàn)
多進(jìn)程共享信號(hào)燈,因此所有非0值的pshared輸入都將使sem_init()返回-1,且置errno為ENOSYS。初始化好的信號(hào)燈由sem變
量表征,用于以下點(diǎn)燈、滅燈操作。
int sem_destroy(sem_t * sem)
被注銷的信號(hào)燈sem要求已沒有線程在等待該信號(hào)燈,否則返回-1,且置errno為EBUSY。除此之外,LinuxThreads的信號(hào)燈注銷函數(shù)不做其他動(dòng)作。
2. 點(diǎn)燈和滅燈
int sem_post(sem_t * sem)
點(diǎn)燈操作將信號(hào)燈值原子地加1,表示增加一個(gè)可訪問的資源。
int sem_wait(sem_t * sem)
int sem_trywait(sem_t * sem)
sem_wait()為等待燈亮操作,等待燈亮(信號(hào)燈值大于0),然后將信號(hào)燈原子地減1,并返回。sem_trywait()為sem_wait()的非阻塞版,如果信號(hào)燈計(jì)數(shù)大于0,則原子地減1并返回0,否則立即返回-1,errno置為EAGAIN。
3. 獲取燈值
int sem_getvalue(sem_t * sem, int * sval)
讀取sem中的燈計(jì)數(shù),存于*sval中,并返回0。
4. 其他
sem_wait()被實(shí)現(xiàn)為取消點(diǎn),而且在支持原子"比較且交換"指令的體系結(jié)構(gòu)上,sem_post()是唯一能用于異步信號(hào)處理函數(shù)的POSIX異步信號(hào)安全的API。
![]()
![]()
![]()
![]()
回頁首
異步信號(hào)
由于LinuxThreads是在核外使用核內(nèi)輕量級(jí)進(jìn)程實(shí)現(xiàn)的線程,所以基于內(nèi)核的異步信號(hào)操作對(duì)于線程也是有效的。但同時(shí),由于異步信號(hào)總是實(shí)際發(fā)往
某個(gè)進(jìn)程,所以無法實(shí)現(xiàn)POSIX標(biāo)準(zhǔn)所要求的"信號(hào)到達(dá)某個(gè)進(jìn)程,然后再由該進(jìn)程將信號(hào)分發(fā)到所有沒有阻塞該信號(hào)的線程中"原語,而是只能影響到其中一
個(gè)線程。
POSIX異步信號(hào)同時(shí)也是一個(gè)標(biāo)準(zhǔn)C庫提供的功能,主要包括信號(hào)集管理(sigemptyset()、sigfillset()、
sigaddset()、sigdelset()、sigismember()等)、信號(hào)處理函數(shù)安裝(sigaction())、信號(hào)阻塞控制
(sigprocmask())、被阻塞信號(hào)查詢(sigpending())、信號(hào)等待(sigsuspend())等,它們與發(fā)送信號(hào)的kill()
等函數(shù)配合就能實(shí)現(xiàn)進(jìn)程間異步信號(hào)功能。LinuxThreads圍繞線程封裝了sigaction()何raise(),本節(jié)集中討論
LinuxThreads中擴(kuò)展的異步信號(hào)函數(shù),包括pthread_sigmask()、pthread_kill()和sigwait()三個(gè)函數(shù)。
毫無疑問,所有POSIX異步信號(hào)函數(shù)對(duì)于線程都是可用的。
int pthread_sigmask(int how, const sigset_t *newmask, sigset_t
*oldmask)
設(shè)置線程的信號(hào)屏蔽碼,語義與sigprocmask()相同,但對(duì)不允許屏蔽的Cancel信號(hào)和不允許響應(yīng)的Restart信號(hào)進(jìn)行了保護(hù)。被屏蔽的信號(hào)保存在信號(hào)隊(duì)列中,可由sigpending()函數(shù)取出。
int pthread_kill(pthread_t thread, int signo)
向thread號(hào)線程發(fā)送signo信號(hào)。實(shí)現(xiàn)中在通過thread線程號(hào)定位到對(duì)應(yīng)進(jìn)程號(hào)以后使用kill()系統(tǒng)調(diào)用完成發(fā)送。
int sigwait(const sigset_t *set, int *sig)
掛起線程,等待set中指定的信號(hào)之一到達(dá),并將到達(dá)的信號(hào)存入*sig中。POSIX標(biāo)準(zhǔn)建議在調(diào)用sigwait()等待信號(hào)以前,進(jìn)程中所有線程都
應(yīng)屏蔽該信號(hào),以保證僅有sigwait()的調(diào)用者獲得該信號(hào),因此,對(duì)于需要等待同步的異步信號(hào),總是應(yīng)該在創(chuàng)建任何線程以前調(diào)用
pthread_sigmask()屏蔽該信號(hào)的處理。而且,調(diào)用sigwait()期間,原來附接在該信號(hào)上的信號(hào)處理函數(shù)不會(huì)被調(diào)用。
如果在等待期間接收到Cancel信號(hào),則立即退出等待,也就是說sigwait()被實(shí)現(xiàn)為取消點(diǎn)。
![]()
![]()
![]()
![]()
回頁首
其他同步方式
除了上述討論的同步方式以外,其他很多進(jìn)程間通信手段對(duì)于LinuxThreads也是可用的,比如基于文件系統(tǒng)的IPC(管道、Unix域Socket等)、消息隊(duì)列(Sys.V或者Posix的)、System
V的信號(hào)燈等。只有一點(diǎn)需要注意,LinuxThreads在核內(nèi)是作為共享存儲(chǔ)區(qū)、共享文件系統(tǒng)屬性、共享信號(hào)處理、共享文件描述符的獨(dú)立進(jìn)程看待的。
關(guān)于作者
![]()
![]()
楊沙洲,男,現(xiàn)攻讀國(guó)防科大計(jì)算機(jī)學(xué)院計(jì)算機(jī)軟件方向博士學(xué)位。您可以通過電子郵件
[email=pubb@163.net?cc=pubb@163.net]pubb@163.net[/email]
跟他聯(lián)系。
本文來自ChinaUnix博客,如果查看原文請(qǐng)點(diǎn):http://blog.chinaunix.net/u1/43662/showart_2135484.html |
|