C++ 多執行緒併發控制——互斥鎖 pthread_mutex
問題描述:有兩個執行緒,主執行緒負責接收資料,並暫時儲存在記憶體中,當記憶體中數量達到一定資料量時,批量提交到oracle中;另一個執行緒作為提交執行緒,定時檢查一遍,不論記憶體中資料量達到多少,定期將資料提交到oracle中。兩個執行緒併發進行,第一個寫入記憶體或者資料庫的時候,提交執行緒需要掛起,反之,主執行緒也需要被掛起。於是,特意來了解一下C++多執行緒中互斥鎖的概念,簡單的應用一下。
--------------------------------------------------------------------------------------------------------------------------------
1.引言:
互斥鎖,是一種訊號量,常用來防止兩個程序或執行緒在同一時刻訪問相同的共享資源。可以保證以下三點:
原子性:把一個互斥量鎖定為一個原子操作,這意味著作業系統(或pthread函式庫)保證瞭如果一個執行緒鎖定了一個互斥量,沒有其他執行緒在同一時間可以成功鎖定這個互斥量。
唯一性:如果一個執行緒鎖定了一個互斥量,在它解除鎖定之前,沒有其他執行緒可以鎖定這個互斥量。
非繁忙等待:如果一個執行緒已經鎖定了一個互斥量,第二個執行緒又試圖去鎖定這個互斥量,則第二個執行緒將被掛起(不佔用任何cpu資源),直到第一個執行緒解除對這個互斥量的鎖定為止,第二個執行緒則被喚醒並繼續執行,同時鎖定這個互斥量。
從以上三點,我們看出可以用互斥量來保證對變數(關鍵的程式碼段)的排他性訪問。
2.函式說明:
需要的標頭檔案:pthread.h
1)初始化互斥鎖
函式原型:int pthread_mutex_init(pthread_mutex_t *mp, const pthread_mutexattr_t *mattr)
引數說明:mp 互斥鎖地址 mattr 屬性通常預設 null
初始化互斥鎖之前,必須將其所在的記憶體清零。
如果互斥鎖已初始化,則它會處於未鎖定狀態。互斥鎖可以位於程序之間共享的記憶體中或者某個程序的專用記憶體中。
2)鎖定互斥鎖
函式原型:
int pthread_mutex_lock(pthread_mutex_t *mutex); #include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_ mutex_lock(&mp); /* acquire the mutex */
函式說明:
當 pthread_mutex_lock() 返回時,該互斥鎖已被鎖定。呼叫執行緒是該互斥鎖的屬主。如果該互斥鎖已被另一個執行緒鎖定和擁有,則呼叫執行緒將阻塞,直到該互斥鎖變為可用為止。
如果互斥鎖型別為 PTHREAD_MUTEX_NORMAL,則不提供死鎖檢測。嘗試重新鎖定互斥鎖會導致死鎖。如果某個執行緒嘗試解除鎖定的互斥鎖不是由該執行緒鎖定或未鎖定,則將產生不確定的行為。
如果互斥鎖型別為 PTHREAD_MUTEX_ERRORCHECK,則會提供錯誤檢查。如果某個執行緒嘗試重新鎖定的互斥鎖已經由該執行緒鎖定,則將返回錯誤。如果某個執行緒嘗試解除鎖定的互斥鎖不是由該執行緒鎖定或者未鎖定,則將返回錯誤。
如果互斥鎖型別為 PTHREAD_MUTEX_RECURSIVE,則該互斥鎖會保留鎖定計數這一概念。執行緒首次成功獲取互斥鎖時,鎖定計數會設定為 1。執行緒每重新鎖定該互斥鎖一次,鎖定計數就增加 1。執行緒每解除鎖定該互斥鎖一次,鎖定計數就減小 1。鎖定計數達到 0 時,該互斥鎖即可供其他執行緒獲取。如果某個執行緒嘗試解除鎖定的互斥鎖不是由該執行緒鎖定或者未鎖定,則將返回錯誤。
如果互斥鎖型別是 PTHREAD_MUTEX_DEFAULT,則嘗試以遞迴方式鎖定該互斥鎖將產生不確定的行為。對於不是由呼叫執行緒鎖定的互斥鎖,如果嘗試解除對它的鎖定,則會產生不確定的行為。如果嘗試解除鎖定尚未鎖定的互斥鎖,則會產生不確定的行為。
返回值:
pthread_mutex_lock() 在成功完成之後會返回零。其他任何返回值都表示出現了錯誤。如果出現以下任一情況,該函式將失敗並返回對應的值。
EAGAIN:由於已超出了互斥鎖遞迴鎖定的最大次數,因此無法獲取該互斥鎖。
EDEADLK:當前執行緒已經擁有互斥鎖。
3)解除鎖定互斥鎖
函式原型:
int pthread_mutex_unlock(pthread_mutex_t *mutex); #include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_mutex_unlock(&mutex); /* release the mutex */
函式說明:pthread_mutex_unlock() 可釋放 mutex 引用的互斥鎖物件。互斥鎖的釋放方式取決於互斥鎖的型別屬性。如果呼叫pthread_mutex_unlock() 時有多個執行緒被 mutex 物件阻塞,則互斥鎖變為可用時排程策略可確定獲取該互斥鎖的執行緒。對於PTHREAD_MUTEX_RECURSIVE 型別的互斥鎖,當計數達到零並且呼叫執行緒不再對該互斥鎖進行任何鎖定時,該互斥鎖將變為可用。
返回值:pthread_mutex_unlock() 在成功完成之後會返回零。
其他任何返回值都表示出現了錯誤。如果出現以下情況,該函式將失敗並返回對應的值。
EPERM :當前執行緒不擁有互斥鎖。
4)使用非阻塞互斥鎖鎖定
函式原型:
int pthread_mutex_trylock(pthread_mutex_t *mutex); #include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_mutex_trylock(&mutex); /* try to lock the mutex */
函式說明:pthread_mutex_trylock() 是 pthread_mutex_lock() 的非阻塞版本。如果 mutex 所引用的互斥物件當前被任何執行緒(包括當前執行緒)鎖定,則將立即返回該呼叫。否則,該互斥鎖將處於鎖定狀態,呼叫執行緒是其屬主。
返回值:pthread_mutex_trylock() 在成功完成之後會返回零。其他任何返回值都表示出現了錯誤。如果出現以下任一情況,該函式將失敗並返回對應的值。
EBUSY :
由於 mutex 所指向的互斥鎖已鎖定,因此無法獲取該互斥鎖。
EAGAIN:描述:
由於已超出了 mutex 的遞迴鎖定最大次數,因此無法獲取該互斥鎖。
5)銷燬互斥鎖
函式原型:
int pthread_mutex_destroy(pthread_mutex_t *mp); #include <pthread.h> pthread_mutex_t mp; int ret; ret = pthread_mutex_destroy(&mp); /* mutex is destroyed */請注意,沒有釋放用來儲存互斥鎖的空間。
返回值:
pthread_mutex_destroy() 在成功完成之後會返回零。其他任何返回值都表示出現了錯誤。如果出現以下任一情況,該函式將失敗並返回對應的值。
EINVAL: mp 指定的值不會引用已初始化的互斥鎖物件。
3.例子:
互斥鎖用來保證一段時間內只有一個執行緒在執行一段程式碼。必要性顯而易見:假設各個執行緒向同一個檔案順序寫入資料,最後得到的結果一定是災難性的。
我們先看下面一段程式碼。這是一個讀/寫程式,它們公用一個緩衝區,並且我們假定一個緩衝區只能儲存一條資訊。即緩衝區只有兩個狀態:有資訊或沒有資訊。
void reader_function ( void );
void writer_function ( void );
char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;
struct timespec delay;
void main ( void ){
pthread_t reader;
/* 定義延遲時間*/
delay.tv_sec = 2;
delay.tv_nec = 0;
/* 用預設屬性初始化一個互斥鎖物件*/
pthread_mutex_init (&mutex,NULL);
pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);
writer_function( );
}
void writer_function (void){
while(1){
/* 鎖定互斥鎖*/
pthread_mutex_lock (&mutex);
if (buffer_has_item==0){
buffer=make_new_item( );
buffer_has_item=1;
}
/* 開啟互斥鎖*/
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
void reader_function(void){
while(1){
pthread_mutex_lock(&mutex);
if(buffer_has_item==1){
consume_item(buffer);
buffer_has_item=0;
}
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
程式說明:
這裡聲明瞭互斥鎖變數mutex,結構pthread_mutex_t為不公開的資料型別,其中包含一個系統分配的屬性物件。函式pthread_mutex_init用來生成一個互斥鎖。NULL引數表明使用預設屬性。如果需要宣告特定屬性的互斥鎖,須呼叫函式pthread_mutexattr_init。函式pthread_mutexattr_setpshared和函式pthread_mutexattr_settype用來設定互斥鎖屬性。前一個函式設定屬性pshared,它有兩個取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用來不同程序中的執行緒同步,後者用於同步本程序的不同執行緒。
在上面的例子中,我們使用的是預設屬性PTHREAD_PROCESS_ PRIVATE。後者用來設定互斥鎖型別,可選的型別有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它們分別定義了不同的上鎖、解鎖機制,一般情況下,選用最後一個預設屬性。
pthread_mutex_lock宣告開始用互斥鎖上鎖,此後的程式碼直至呼叫pthread_mutex_unlock為止,均被上鎖,即同一時間只能被一個執行緒呼叫執行。當一個執行緒執行到pthread_mutex_lock處時,如果該鎖此時被另一個執行緒使用,那此執行緒被阻塞,即程式將等待到另一個執行緒釋放此互斥鎖。在上面的例子中,我們使用了pthread_delay_np函式,讓執行緒睡眠一段時間,就是為了防止一個執行緒始終佔據此函式。
4.飢餓和死鎖的情形
當一個互斥量已經被別的執行緒鎖定後,另一個執行緒呼叫pthread_mutex_lock()函式去鎖定它時,會掛起自己的執行緒等待這個互斥量被解鎖。可能出現以下兩種情況:
“飢餓狀態”:這個互斥量一直沒有被解鎖,等待鎖定它的執行緒將一直被掛著,即它請求某個資源,但永遠得不到它。使用者必須在程式中努力避免這種“飢餓”狀態出現。Pthread函式庫不會自動處理這種情況。
“死鎖”:一組執行緒中的所有執行緒都在等待被同組中另外一些執行緒佔用的資源,這時,所有執行緒都因等待互斥量而被掛起,它們中的任何一個都不可能恢復執行,程式無法繼續執行下去。這時就產生了死鎖。Pthread函式庫可以跟蹤這種情形,最後一個執行緒試圖呼叫pthread_mutex_lock()時會失敗,並返回型別為EDEADLK的錯誤。