Linux--Linux互斥鎖、條件變量和信號量

轉載自 http://hi.baidu.com/yanxionglu/blog/item/a3eafdece87015f0b2fb95cd.html

進行多線程編程,最應該注意的就是那些共享的數據,因爲無法知道哪個線程會在哪個時候對它進行操作,也無法得知哪個線程會先運行,哪個線程會後運行。所以,要對這些資源進行合理的分配和正確的使用。在Linux下,提供了互斥鎖、條件變量和信號量來對共享資源進行保護。


一、互斥鎖
互斥鎖,是一種信號量,常用來防止兩個進程或線程在同一時刻訪問相同的共享資源。
需要的頭文件:pthread.h
互斥鎖標識符:pthread_mutex_t

(1)互斥鎖初始化:
函數原型: int pthread_mutex_init (pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr);
函數傳入值:  mutex:互斥鎖。
mutexattr:PTHREAD_MUTEX_INITIALIZER 創建快速互斥鎖。
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP 創建遞歸互斥鎖。
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP  創建檢錯互斥鎖。
函數返回值:成功:0;出錯:-1

(2)互斥操作函數
int pthread_mutex_lock(pthread_mutex_t* mutex); //上鎖
int pthread_mutex_trylock (pthread_mutex_t* mutex); //只有在互斥被鎖住的情況下才阻塞
int pthread_mutex_unlock (pthread_mutex_t* mutex); //解鎖
int pthread_mutex_destroy (pthread_mutex_t* mutex); //清除互斥鎖
函數傳入值:mutex:互斥鎖。
函數返回值:成功:0;出錯:-1

使用形式:
pthread_mutex_t mutex;
pthread_mutex_init (&mutex, NULL); /*定義*/
...

pthread_mutex_lock(&mutex); /*獲取互斥鎖*/
... /*臨界資源*/
pthread_mutex_unlock(&mutex); /*釋放互斥鎖*/

如果一個線程已經給一個互斥量上鎖了,後來在操作的過程中又再次調用了該上鎖的操作,那麼該線程將會無限阻塞在這個地方,從而導致死鎖。這就需要互斥量的屬性。

互斥量分爲下面三種:
1、快速型。這種類型也是默認的類型。該線程的行爲正如上面所說的。
2、遞歸型。如果遇到我們上面所提到的死鎖情況,同一線程循環給互斥量上鎖,那麼系統將會知道該上鎖行爲來自同一線程,那麼就會同意線程給該互斥量上鎖。
3、錯誤檢測型。如果該互斥量已經被上鎖,那麼後續的上鎖將會失敗而不會阻塞,pthread_mutex_lock()操作將會返回EDEADLK。

互斥量的屬性類型爲pthread_mutexattr_t。聲明後調用pthread_mutexattr_init()來創建該互斥量。然後調用pthread_mutexattr_settype來設置屬性。格式如下:int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind);
第一個參數attr,就是前面聲明的屬性變量;第二個參數kind,就是我們要設置的屬性類型。他有下面幾個選項:
PTHREAD_MUTEX_FAST_NP
PTHREAD_MUTEX_RECURSIVE_NP
PTHREAD_MUTEX_ERRORCHECK_NP

下面給出一個使用屬性的簡單過程:
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(&mutex,&attr);
pthread_mutex_destroy(&attr);

前面我們提到在調用pthread_mutex_lock()的時候,如果此時mutex已經被其他線程上鎖,那麼該操作將會一直阻塞在這個地方。如果我們此時不想一直阻塞在這個地方,那麼可以調用下面函數:pthread_mutex_trylock。
如果此時互斥量沒有被上鎖,那麼pthread_mutex_trylock將會返回0,並會對該互斥量上鎖。如果互斥量已經被上鎖,那麼會立刻返回EBUSY。

二、條件變量
需要的頭文件:pthread.h
條件變量標識符:pthread_cond_t

1、互斥鎖的存在問題:
互斥鎖一個明顯的缺點是它只有兩種狀態:鎖定和非鎖定。設想一種簡單情景:多個線程訪問同一個共享資源時,並不知道何時應該使用共享資源,如果在臨界區里加入判斷語句,或者可以有效,但一來效率不高,二來複雜環境下就難以編寫了,這是我們需要一個結構,能在條件成立時觸發相應線程,進行變量修改和訪問。

2、條件變量:
條件變量通過允許線程阻塞和等待另一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。使用時,條件變量被用來阻塞一個線程,當條件不滿足時,線程往往解開相應的互斥鎖並等待條件發生變化。一旦其它的某個線程改變了條件變量,它將通知相應的條件變量喚醒一個或多個正被此條件變量阻塞的線程。這些線程將重新鎖定互斥鎖並重新測試條件是否滿足。

3、條件變量的相關函數
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //條件變量結構
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t*cond_attr);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
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);
int pthread_cond_destroy(pthread_cond_t *cond);

詳細說明:
(1)創建和註銷
條件變量和互斥鎖一樣,都有靜態動態兩種創建方式
a.靜態方式
靜態方式使用PTHREAD_COND_INITIALIZER常量,如下:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
b.動態方式
動態方式調用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)等待和激發
a.等待
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()前的加鎖動作對應。
b.激發
激發條件有兩種形式,pthread_cond_signal()激活一個等待該條件的線程,存在多個等待線程時按入隊順序激活其中一個;而pthread_cond_broadcast()則激活所有等待線程。

(3)其他操作
pthread_cond_wait ()和pthread_cond_timedwait()都被實現爲取消點,因此,在該處等待的線程將立即重新運行,在重新鎖定mutex後離開 pthread_cond_wait(),然後執行取消動作。也就是說如果pthread_cond_wait()被取消,mutex是保持鎖定狀態的,因而需要定義退出回調函數來爲其解鎖。
pthread_cond_wait實際上可以看作是以下幾個動作的合體:
解鎖線程鎖;
等待條件爲true;
加鎖線程鎖;

使用形式:
// 線程一代碼
pthread_mutex_lock(&mutex);
if (條件滿足)
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

// 線程二代碼
pthread_mutex_lock(&mutex);
while (條件不滿足)
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
/*線程二中爲什麼使用while呢?因爲在pthread_cond_signal和pthread_cond_wait返回之間,有時間差,假設在這個時間差內,條件改變了,顯然需要重新檢查條件。也就是說在pthread_cond_wait被喚醒的時候可能該條件已經不成立。*/

三、信號量
信號量其實就是一個計數器,也是一個整數。每一次調用wait操作將會使semaphore值減一,而如果semaphore值已經爲0,則wait操作將會阻塞。每一次調用post操作將會使semaphore值加一。
需要的頭文件:semaphore.h
信號量標識符:sem_t

主要函數:
(1)sem_init
功能:         用於創建一個信號量,並初始化信號量的值。
函數原型:     int sem_init (sem_t* sem, int pshared, unsigned int value);
函數傳入值:   sem:信號量。
pshared:決定信號量能否在幾個進程間共享。由於目前LINUX還沒有實現進程間共享信息量,所以這個值只能取0。
value:初始計算器
函數返回值:   0:成功;-1:失敗。

(2)其他函數。
//等待信號量
int sem_wait (sem_t* sem);
int sem_trywait (sem_t* sem);
//發送信號量
int sem_post (sem_t* sem);
//得到信號量值
int sem_getvalue (sem_t* sem);
//刪除信號量
int sem_destroy (sem_t* sem);
功能:sem_wait和sem_trywait相當於P操作,它們都能將信號量的值減一,兩者的區別在於若信號量的值小於零時,sem_wait將會阻塞進程,而sem_trywait則會立即返回。
sem_post相當於V操作,它將信號量的值加一,同時發出喚醒的信號給等待的進程(或線程)。
sem_getvalue 得到信號量的值。
sem_destroy 摧毀信號量。

使用形式:
sem_t sem;
sem_init(&sem, 0, 1); /*信號量初始化*/  
...

sem_wait(&sem);   /*等待信號量*/
... /*臨界資源*/
sem_post(&sem);   /*釋放信號量*/

信號量與線程鎖、條件變量相比還有以下幾點不同:
1)鎖必須是同一個線程獲取以及釋放,否則會死鎖。而條件變量和信號量則不必。
2)信號的遞增與減少會被系統自動記住,系統內部有一個計數器實現信號量,不必擔心會丟失,而喚醒一個條件變量時,如果沒有相應的線程在等待該條件變量,這次喚醒將被丟失。
發佈了23 篇原創文章 · 獲贊 1 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章