我們可以通過pthread提供的互斥量接口來保護我們的資料,確定每次隻有一個線程通路。從本質上說就是一把鎖,我們在通路共享資料的時候設定(上鎖),在通路完成後釋放(解鎖)。當我們解鎖互斥量的時候,如果有多餘一個的線程被阻塞,則所有阻塞在這個鎖的程序都被喚醒,變成可以運作的狀态。接下來,隻有一個線程開始運作并設定鎖,其他的看到互斥量仍然是被鎖定,繼續等待。
1. 初始化:
線程的互斥量資料類型是pthread_mutex_t. 在使用前, 要對它進行初始化:
對于靜态配置設定的互斥量, 可以初始化為PTHREAD_MUTEX_INITIALIZER(等價于 pthread_mutex_init(…, NULL))或調用pthread_mutex_init。
對于動态配置設定的互斥量, 在申請記憶體(malloc)之後, 通過pthread_mutex_init進行初始化, 并且在釋放記憶體(free)前需要調用pthread_mutex_destroy.
原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restric attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
傳回值:成功則傳回0, 出錯則傳回錯誤編号.
說明:1、如果使用預設的屬性初始化互斥量, 隻需把attr設為NULL。
2、銷毀一個互斥鎖即意味着釋放它所占用的資源,且要求鎖目前處于開放狀态。由于在Linux中,互斥鎖并不占用任何資源,是以LinuxThreads中的pthread_mutex_destroy()除了檢查鎖狀态以外(鎖定狀态則傳回EBUSY)沒有其他動作。
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);
傳回值:成功則傳回0, 出錯則傳回錯誤編号.
說明:1、想給一個互斥量上鎖,我們調用pthread_mutex_lock。如果mutex已經上鎖,調用的線程将會被阻塞,直至信号量解鎖。
2、具體說一下trylock函數, 這個函數是非阻塞調用模式, 也就是說, 如果互斥量沒被鎖住, trylock函數将把互斥量加鎖, 并獲得對共享資源的通路權限;如果互斥量被鎖住了, trylock函數将不會阻塞等待而直接傳回EBUSY, 表示共享資源處于忙狀态。
3、要解鎖一個信号量,我們調用phtread_mutex_unlock。
例子:
我們使用mutex來保護資料結構:當多個程序需要通路動态申請的結構,我們嵌入了引用計數,來保證知道所有線程都使用完它時,我們才釋放它。
#include <pthread.h>
#include <stdlib.h>
struct foo{
int f_count;
pthread_mutex_t f_lock;
};
struct foo * foo_alloc(void){
struct foo *fp;
if((fp = malloc(sizeof(struct foo))) != NULL){
fp->f_count = 1;
if(pthread_mutex_init(&fp->f_lock,NULL) != 0){
free(fp);
return NULL;
}
}
return fp;
}
void foo_hold(struct foo *fp){
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}
void foo_rele(struct foo *fp){
pthread_mutex_lock(&fp->f_lock);
if(--fp->f_count == 0){
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
}else{
pthread_mutex_unlock(&fp->f_lock);
}
}
死鎖:
有時,可能需要同時通路兩個資源。您可能正在使用其中的一個資源,随後發現還需要另一個資源。如果兩個線程嘗試聲明這兩個資源,但是以不同的順序鎖定與這些資源相關聯的互斥鎖,則會出現問題。例如,如果兩個線程分别鎖定互斥鎖 1 和互斥鎖 2,則每個線程嘗試鎖定另一個互斥鎖時,将會出現死鎖。下面的例子說明了可能的死鎖情況。
線程 1 | 線程 2 |
---|---|
pthread_mutex_lock(&m1); pthread_mutex_lock(&m2); pthread_mutex_unlock(&m2); pthread_mutex_unlock(&m1); | pthread_mutex_lock(&m2); pthread_mutex_lock(&m1); pthread_mutex_unlock(&m1); pthread_mutex_unlock(&m2); |
避免此問題的最佳方法是,確定線程在鎖定多個互斥鎖時,以同樣的順序進行鎖定。如果始終按照規定的順序鎖定,就不會出現死鎖。此方法稱為鎖分層結構,它通過為互斥鎖指定邏輯編号來對這些鎖進行排序。
另外,請注意以下限制:如果您持有的任何互斥鎖其指定編号大于 n,則不能提取指定編号為 n 的互斥鎖。
但是,不能始終使用此方法。有時,必須按照與規定不同的順序提取互斥鎖。要防止在這種情況下出現死鎖,請使用 pthread_mutex_trylock()。如果線程發現無法避免死鎖時,該線程必須釋放其互斥鎖。
總體來講, 有幾個不成文的基本原則:
對共享資源操作前一定要獲得鎖。
完成操作以後一定要釋放鎖。
盡量短時間地占用鎖。
如果有多鎖, 如獲得順序是ABC連環扣, 釋放順序也應該是ABC。
線程錯誤傳回時應該釋放它所獲得的鎖。
其他詳細資料參考:
互斥鎖屬性 http://docs.sun.com/app/docs/doc/819-7051/6n919hpaf?l=zh&a=view
使用互斥鎖 http://docs.sun.com/app/docs/doc/819-7051/6n919hpag?l=zh&a=view