1、互斥量屬性
值得注意的兩個屬性是進程共享屬性和類型屬性
#include <pthread.h>
int pthread_mutexattr_init( pthread_mutexattr_t *attr );
int pthread_mutexattr_destroy( pthread_mutexattr_t *attr );
int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr,int *pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared);
返回值:若成功則返回0,否則返回錯誤編號
pshared的取值
- THREAD_PROCESS_SHARED:那麼由這個屬性對象創建的互斥鎖將被保存在共享內存中,可以被多個進程中的線程共享。
- PTHREAD_PROCESS_PRIVATE:那麼只有和創建這個互斥鎖的線程在同一個進程中的線程才能訪問這個互斥鎖。
int pthread_mutexattr_settype(pthread_mutexattr_t *attr,int kind);
int pthread_mutexattr_gettype(pthread_mutexattr_t *attr,int *kind);
- PTHREAD_MUTEX_NORMAL,不進行deadlock detection(死鎖檢測)。企圖進行relock這個mutex會導致deadlock.如果一個線程對未加鎖的或已經unlock的mutex對象進行unlock操作,結果是不未知的。
- PTHREAD_MUTEX_ERRORCHECK,那麼將進行錯誤檢查。如果一個線程企圖對一個已經鎖住的mutex進行relock,將返回一個錯誤。如果一個線程對未加鎖的或已經unlock的mutex對象進行unlock操作,將返回一個錯誤。
- PTHREAD_MUTEX_RECURSIVE,mutex會有一個鎖住次數(lock count)的概念。當一個線程成功地第一次鎖住一個mutex的時候,鎖住次數(lock count)被設置爲1,每一次一個線程unlock這個mutex的時候,鎖住次數(lock count)就減1。當鎖住次數(lock count)減少爲0的時候,其他線程就能獲得該mutex鎖了。如果一個線程對未加鎖的或已經unlock的mutex對象進行unlock操作,將返回一個錯誤,如果一個線程對這種類型的互斥鎖重複上鎖,不會引起死鎖,一個線程對這類互斥鎖的多次重複上鎖必須由這個線程來重複相同數量的解鎖,這樣才能解開這個互斥鎖,別的線程才能得到這個互斥鎖。如果試圖解鎖一個由別的進程鎖定的互斥鎖將會返回一個錯誤代碼。如果一個線程試圖解鎖已經被解鎖的互斥鎖也將會返回一個錯誤代碼。這種類型的互斥鎖只能是進程私有的(作用域屬性爲PTHREAD_PROCESS_PRIVATE)。
- PTHREAD_MUTEX_DEFAULT,企圖遞歸的獲取這個mutex的鎖的結果是不確定的。unlock一個不是被調用線程鎖住的mutex的結果也是不確定的。企圖unlock一個未被鎖住的mutex導致不確定的結果。
2、遞歸鎖
Linux下的pthread_mutex_t鎖默認是非遞歸的
根據上文分析,在同一個線程中,如果想要多次獲得一個鎖,只能使用遞歸鎖PTHREAD_MUTEX_RECURSIVE,不然會出現死鎖,其次,遞歸鎖是不被提倡的,用到遞歸鎖說明這個代碼設計是有問題的。更好的做法是,提取出一個被兩個個公有函數調用的私有函數,這個私有函數無需鎖定mutex
2.1 非遞歸鎖出現死鎖的情況
main()
{
func1(x);
......
func2(x);
}
func1()
{
pthread_mutex_lock(x->lock);
......
func2(x);
......
pthread_mutex_unlock(x->lock);
}
func2()
{
pthread_mutex_lock(x->lock);
......
pthread_mutex_unlock(x->lock);
}
2.2 避免使用遞歸鎖
兩種辦法:
- 將原來的加鎖的函數分爲不加鎖的部分
- 作爲對外接口的public函數只能調用無鎖的私有變量函數,而不能互相調用
main()
{
func1(x);
......
func2(x);
}
func1()
{
pthread_mutex_lock(x->lock);
......
func2_locked(x); //這裏調用func2的非鎖函數func2_locked(x),可以避免出現遞歸
......
pthread_mutex_unlock(x->lock);
}
func2()
{
pthread_mutex_lock(x->lock);
func2_locked(x);
pthread_mutex_unlock(x->lock);
}
//111111原來的版本
MutexLock mutex;
void foo()
{
mutex.lock();
// do something
mutex.unlock();
}
void bar()
{
mutex.lock();
// do something
foo();
mutex.unlock();
}
// 222222222不加鎖版本
void foo_nolock()
{
// do something
}
// 加鎖版本
void fun()
{
mutex.lock();
foo_nolock();
mutex.unlock();
}
// 33333333private版本
class T
{
public:
foo(); //加鎖
bar(); //加鎖
private:
foo_nolock();
bar_nolock();
}
作爲對外接口的public函數只能調用無鎖的私有變量函數,而不能互相調用。在函數具體實現上,這兩種方法基本是一樣的。
2.3 讀寫鎖遞歸
- 先讀在寫,阻塞
- 先寫在讀,死鎖
- 先讀後讀,正常
- 先寫在寫,死鎖
在POSIX標準中,如果一個線程先獲得寫鎖,又獲得讀鎖,則結果是無法預測的。這就是爲什麼程序1的運行出人所料。需要注意的是,讀鎖是遞歸鎖(即可重入),寫鎖是非遞歸鎖(即不可重入)。因此程序3不會死鎖,而程序4會一直阻塞。
#include <pthread.h>
int main()
{
pthread_rwlock_t rwl;
pthread_rwlock_rdlock(&rwl);
pthread_rwlock_wrlock(&rwl);
pthread_rwlock_unlock(&rwl);
pthread_rwlock_unlock(&rwl);
return -1;
}
/*程序2*/
#include <pthread.h>
int main()
{
pthread_rwlock_t rwl;
pthread_rwlock_wrlock(&rwl);
pthread_rwlock_rdlock(&rwl);
pthread_rwlock_unlock(&rwl);
pthread_rwlock_unlock(&rwl);
return -1;
}
/*程序3*/
#include <pthread.h>
int main()
{
pthread_rwlock_t rwl;
pthread_rwlock_rdlock(&rwl);
pthread_rwlock_rdlock(&rwl);
pthread_rwlock_unlock(&rwl);
pthread_rwlock_unlock(&rwl);
return -1;
}
/*程序4*/
#include <pthread.h>
int main()
{
pthread_rwlock_t rwl;
pthread_rwlock_wrlock(&rwl);
pthread_rwlock_wrlock(&rwl);
pthread_rwlock_unlock(&rwl);
pthread_rwlock_unlock(&rwl);
return -1;
}
3. 其他同步屬性
3.1 讀寫鎖屬性
讀寫鎖與互斥量類似,也具有屬性。用pthread_rwlockattr_init初始化pthread_rwlockattr_t結構,用pthread_rwlockattr_destroy回收結構
#include <pthread.h>
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
兩者的返回值都是:若成功則返回0,否則返回錯誤編號
讀寫鎖支持的唯一屬性是進程共享屬性,該屬性與互斥量的進程共享屬性相同。就像互斥量的進程共享屬性一樣,用一對函數來讀取和設置讀寫鎖的進程共享屬性。
#include <pthread.h>
#include <pthread.h>
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t * restrict attr, int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
兩者的返回值都是:若成功則返回0,否則返回錯誤編號
3.2 條件變量屬性
條件變量也有屬性。與互斥量和讀寫鎖類似,有一對函數用於初始化和回收條件變量屬性。
#include <pthread.h>
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
兩者的返回值都是:若成功則返回0,否則返回錯誤編號
與其他的同步原語一樣,條件變量支持進程共享屬性。
#include <pthread.h>
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);
兩者的返回值都是:若成功則返回0,否則返回錯誤編號
3.3 屏障屬性
屏障也具有屬性。我們可以使用pthread_barrierattr_init函數初始化一個屏障屬性,使用pthread_barrierattr_destroy函數回收屏障屬性。
#include <pthread.h>
int pthread_barrierattr_init(pthread_barrierattr_t *attr);
int pthread_barrierattr_destroy(pthread_barrierattr_t attr);
兩個函數的返回值都是:若成功則返回0,否則返回錯誤編號
唯一的一個屏障屬性是進程共享屬性。
#include <pthread.h>
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr, int *restrict pshared);
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int *pshared);
兩個函數的返回值都是:若成功則返回0,否則返回錯誤編號