Linux–Linux互斥鎖、條件變數和訊號量

來源:互聯網
上載者:User

進行多線程編程,最應該注意的就是那些共用的資料,因為無法知道哪個線程會在哪個時候對它進行操作,也無法得知哪個線程會先運行,哪個線程會後運行。所以,要對這些資源進行合理的分配和正確的使用。在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)訊號的遞增與減少會被系統自動記住,系統內部有一個計數器實現訊號量,不必擔心會丟失,而喚醒一個條件變數時,如果沒有相應的線程在等待該條件變數,這次喚醒將被丟失。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.