讀寫鎖和互斥量(互斥鎖)很類似,是另一種線程同步機制,但不屬于POSIX標(biāo)準(zhǔn),可以用來同步同一進程中的各個線程。當(dāng)然如果一個讀寫鎖存放在多個進程共享的某個內(nèi)存區(qū)中,那么還可以用來進行進程間的同步,
和互斥量不同的是:互斥量會把試圖進入已保護的臨界區(qū)的線程都阻塞;然而讀寫鎖會視當(dāng)前進入臨界區(qū)的線程和請求進入臨界區(qū)的線程的屬性來判斷是否允許線程進入。
相對互斥量只有加鎖和不加鎖兩種狀態(tài),讀寫鎖有三種狀態(tài):讀模式下的加鎖,寫模式下的加鎖,不加鎖。
讀寫鎖的使用規(guī)則:
● 只要沒有寫模式下的加鎖,任意線程都可以進行讀模式下的加鎖;
● 只有讀寫鎖處于不加鎖狀態(tài)時,才能進行寫模式下的加鎖;
讀寫鎖也稱為共享-獨占(shared-exclusive)鎖,當(dāng)讀寫鎖以讀模式加鎖時,它是以共享模式鎖住,當(dāng)以寫模式加鎖時,它是以獨占模式鎖住。讀寫鎖非常適合讀數(shù)據(jù)的頻率遠大于寫數(shù)據(jù)的頻率從的應(yīng)用中。這樣可以在任何時刻運行多個讀線程并發(fā)的執(zhí)行,給程序帶來了更高的并發(fā)度。
需要提到的是:讀寫鎖到目前為止仍然不是屬于POSIX標(biāo)準(zhǔn),本文討論的讀寫鎖函數(shù)都是有Open Group定義的的。例如下面是在我機器上,編譯器是gcc version 4.4.6,關(guān)于讀寫鎖的定義是包含在預(yù)處理命令中的:
#if defined __USE_UNIX98 || defined __USE_XOPEN2K ... 讀寫鎖相關(guān)函數(shù)聲明... #endif |
1、讀寫鎖的初始化和銷毀
/* Initialize read-write lock */ int pthread_rwlock_init (pthread_rwlock_t *__restrict __rwlock, __const pthread_rwlockattr_t *__restrict __attr); /* Destroy read-write lock */ extern int pthread_rwlock_destroy (pthread_rwlock_t *__rwlock); 返回值:成功返回0,否則返回錯誤代碼 |
上面兩個函數(shù)分別由于讀寫鎖的初始化和銷毀。和互斥量,條件變量一樣,如果讀寫鎖是靜態(tài)分配的,可以通過常量進行初始化,如下:
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; |
也可以通過pthread_rwlock_init()進行初始化。對于動態(tài)分配的讀寫鎖由于不能直接賦值進行初始化,只能通過這種方式進行初始化。pthread_rwlock_init()第二個參數(shù)是讀寫鎖的屬性,如果采用默認(rèn)屬性,可以傳入空指針NULL。
那么當(dāng)不在需要使用時及釋放(自動或者手動)讀寫鎖占用的內(nèi)存之前,需要調(diào)用pthread_rwlock_destroy()進行銷毀讀寫鎖占用的資源。
2、互斥鎖的屬性設(shè)置
/* 初始化讀寫鎖屬性對象 */ int pthread_rwlockattr_init (pthread_rwlockattr_t *__attr); /* 銷毀讀寫鎖屬性對象 */ int pthread_rwlockattr_destroy (pthread_rwlockattr_t *__attr); /* 獲取讀寫鎖屬性對象在進程間共享與否的標(biāo)識*/ int pthread_rwlockattr_getpshared (__const pthread_rwlockattr_t * __restrict __attr, int *__restrict __pshared); /* 設(shè)置讀寫鎖屬性對象,標(biāo)識在進程間共享與否 */ int pthread_rwlockattr_setpshared (pthread_rwlockattr_t *__attr, int __pshared); 返回值:成功返回0,否則返回錯誤代碼 |
這個屬性設(shè)置和互斥量的基本一樣,具體可以參考互斥量的設(shè)置互斥量的屬性設(shè)置
3、互斥鎖的使用
/* 讀模式下加鎖 */ int pthread_rwlock_rdlock (pthread_rwlock_t *__rwlock); /* 非阻塞的讀模式下加鎖 */ int pthread_rwlock_tryrdlock (pthread_rwlock_t *__rwlock); # ifdef __USE_XOPEN2K /* 限時等待的讀模式加鎖 */ int pthread_rwlock_timedrdlock (pthread_rwlock_t *__restrict __rwlock, __const struct timespec *__restrict __abstime); # endif /* 寫模式下加鎖 */ int pthread_rwlock_wrlock (pthread_rwlock_t *__rwlock); /* 非阻塞的寫模式下加鎖 */ int pthread_rwlock_trywrlock (pthread_rwlock_t *__rwlock); # ifdef __USE_XOPEN2K /* 限時等待的寫模式加鎖 */ int pthread_rwlock_timedwrlock (pthread_rwlock_t *__restrict __rwlock, __const struct timespec *__restrict __abstime); # endif /* 解鎖 */ int pthread_rwlock_unlock (pthread_rwlock_t *__rwlock); 返回值:成功返回0,否則返回錯誤代碼 |
(1)pthread_rwlock_rdlock()系列函數(shù)
pthread_rwlock_rdlock()用于以讀模式即共享模式獲取讀寫鎖,如果讀寫鎖已經(jīng)被某個線程以讀模式占用,那么調(diào)用線程就被阻塞。在實現(xiàn)讀寫鎖的時候可以對共享模式下鎖的數(shù)量進行限制(目前不知如何限制)。
pthread_rwlock_tryrdlock()和pthread_rwlock_rdlock()的唯一區(qū)別就是,在無法獲取讀寫鎖的時候,調(diào)用線程不會阻塞,會立即返回,并返回錯誤代碼EBUSY。
pthread_rwlock_timedrdlock()是限時等待讀模式加鎖,時間參數(shù)struct timespec * __restrict __abstime也是絕對時間,和條件變量的pthread_cond_timedwait()使用基本一致,具體可以參考pthread_cond_timedwait() 3條件變量的使用
?。?)pthread_rwlock_wrlock()系列函數(shù)
pthread_rwlock_wrlock()用于寫模式即獨占模式獲取讀寫鎖,如果讀寫鎖已經(jīng)被其他線程占用,不論是以共享模式還是獨占模式占用,調(diào)用線程都會進入阻塞狀態(tài)。
pthread_rwlock_trywrlock()在無法獲取讀寫鎖的時候,調(diào)用線程不會進入睡眠,會立即返回,并返回錯誤代碼EBUSY。
pthread_rwlock_timedwrlock()是限時等待寫模式加鎖,也和條件變量的pthread_cond_timedwait()使用基本一致,具體可以參考pthread_cond_timedwait()3條件變量的使用。
(3)pthread_rwlock_unlock()
無論以共享模式還是獨占模式獲得的讀寫鎖,都可以通過調(diào)用pthread_rwlock_unlock()函數(shù)進行釋放該讀寫鎖。
下面是測試代碼:
#include <iostream> #include <cstdlib> #include <unistd.h> #include <pthread.h> using namespace std; struct{ pthread_rwlock_t rwlock; int product; }sharedData = {PTHREAD_RWLOCK_INITIALIZER, 0}; void * produce(void *ptr) { for (int i = 0; i < 5; ++i) { pthread_rwlock_wrlock(&sharedData.rwlock); sharedData.product = i; pthread_rwlock_unlock(&sharedData.rwlock); sleep(1); } } void * consume1(void *ptr) { for (int i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); cout<<"consume1:"<<sharedData.product<<endl; pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); } } void * consume2(void *ptr) { for (int i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); cout<<"consume2:"<<sharedData.product<<endl; pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); } } int main() { pthread_t tid1, tid2, tid3; pthread_create(&tid1, NULL, produce, NULL); pthread_create(&tid2, NULL, consume1, NULL); pthread_create(&tid3, NULL, consume2, NULL); void *retVal; pthread_join(tid1, &retVal); pthread_join(tid2, &retVal); pthread_join(tid3, &retVal); return 0; } |
測試結(jié)果如下:
consume1:0 consume2:0 consume2:0 consume1:1 consume2:1 consume1:2 consume2:2 consume1:3 consume2:3 consume1:4 |
如果把consume1的解鎖注釋掉,如下:
void * consume1(void *ptr) { for (int i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); cout<<"consume1:"<<sharedData.product<<endl; //pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); } } |
程序的執(zhí)行結(jié)果如下:
consume1:0 consume2:0 consume2:0 consume1:0 consume2:0 consume1:0 consume2:0 consume1:0 consume2:0 consume1:0 |
從執(zhí)行結(jié)果可以看出Open Group提供的讀寫鎖函數(shù)是優(yōu)先考慮等待讀模式占用鎖的線程,這種實現(xiàn)的一個很大缺陷就是出現(xiàn)寫入線程餓死的情況。