問題描述:有兩個線程,主線程負責接收資料,並暫時儲存在記憶體中,當記憶體中數量達到一定資料量時,批量提交到oracle中;另一個線程作為提交線程,定時檢查一遍,不論記憶體中資料量達到多少,定期將資料提交到oracle中。兩個線程並發進行,第一個寫入記憶體或者資料庫的時候,提交線程需要掛起,反之,主線程也需要被掛起。於是,特意來瞭解一下C++多線程中互斥鎖的概念,簡單的應用一下。
--------------------------------------------------------------------------------------------------------------------------------
這篇文章寫的簡單易懂,值得新手學習:http://blog.chinaunix.net/uid-21411227-id-1826888.html
另外,還有一篇文章提供了一個封裝好的互斥鎖程式,學習一下:http://blog.csdn.net/chexlong/article/details/7058283
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的錯誤。