天天看點

線程同步——互斥量

我們可以通過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

繼續閱讀