執行緒同步與互斥:讀寫鎖
讀寫鎖基本原理
當有一個執行緒已經持有互斥鎖時,互斥鎖將所有試圖進入臨界區的執行緒都阻塞住。但是考慮一種情形,當前持有互斥鎖的執行緒只是要讀訪問共享資源,而同時有其它幾個執行緒也想讀取這個共享資源,但是由於互斥鎖的排它性,所有其它執行緒都無法獲取鎖,也就無法讀訪問共享資源了,但是實際上多個執行緒同時讀訪問共享資源並不會導致問題。
在對資料的讀寫操作中,更多的是讀操作,寫操作較少,例如對資料庫資料的讀寫應用。為了滿足當前能夠允許多個讀出,但只允許一個寫入的需求,執行緒提供了讀寫鎖來實現。
讀寫鎖的特點如下:
1)如果有其它執行緒讀資料,則允許其它執行緒執行讀操作,但不允許寫操作。
2)如果有其它執行緒寫資料,則其它執行緒都不允許讀、寫操作。
讀寫鎖分為讀鎖和寫鎖,規則如下:
1)如果某執行緒申請了讀鎖,其它執行緒可以再申請讀鎖,但不能申請寫鎖。
2)如果某執行緒申請了寫鎖,其它執行緒不能申請讀鎖,也不能申請寫鎖。
POSIX 定義的讀寫鎖的資料型別是: pthread_rwlock_t。
讀寫鎖基本操作
以下函式所需標頭檔案:
#include <pthread.h>
1)初始化讀寫鎖
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
功能:
用來初始化 rwlock 所指向的讀寫鎖。
引數:
rwlock:指向要初始化的讀寫鎖指標。
attr:讀寫鎖的屬性指標。如果 attr 為 NULL 則會使用預設的屬性初始化讀寫鎖,否則使用指定的 attr 初始化讀寫鎖。
可以使用巨集 PTHREAD_RWLOCK_INITIALIZER 靜態初始化讀寫鎖,比如:
這種方法等價於使用 NULL 指定的 attr 引數呼叫 pthread_rwlock_init() 來完成動態初始化,不同之處在於PTHREAD_RWLOCK_INITIALIZER 巨集不進行錯誤檢查。pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITIALIZER;
返回值:
成功:0,讀寫鎖的狀態將成為已初始化和已解鎖。
失敗:非 0 錯誤碼。
2)申請讀鎖
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock );
功能:
以阻塞方式在讀寫鎖上獲取讀鎖(讀鎖定)。如果沒有寫者持有該鎖,並且沒有寫者阻塞在該鎖上,則呼叫執行緒會獲取讀鎖。如果呼叫執行緒未獲取讀鎖,則它將阻塞直到它獲取了該鎖。一個執行緒可以在一個讀寫鎖上多次執行讀鎖定。執行緒可以成功呼叫 pthread_rwlock_rdlock() 函式 n 次,但是之後該執行緒必須呼叫 pthread_rwlock_unlock() 函式 n 次才能解除鎖定。
引數:
rwlock:讀寫鎖指標。
返回值:
成功:0
失敗:非 0 錯誤碼
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
用於嘗試以非阻塞的方式來在讀寫鎖上獲取讀鎖。如果有任何的寫者持有該鎖或有寫者阻塞在該讀寫鎖上,則立即失敗返回。
3)申請寫鎖
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock );
功能:
在讀寫鎖上獲取寫鎖(寫鎖定)。如果沒有寫者持有該鎖,並且沒有寫者讀者持有該鎖,則呼叫執行緒會獲取寫鎖。如果呼叫執行緒未獲取寫鎖,則它將阻塞直到它獲取了該鎖。
引數:
rwlock:讀寫鎖指標。
返回值:
成功:0
失敗:非 0 錯誤碼
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
用於嘗試以非阻塞的方式來在讀寫鎖上獲取寫鎖。如果有任何的讀者或寫者持有該鎖,則立即失敗返回。
4)解鎖
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);
功能:
無論是讀鎖或寫鎖,都可以通過此函式解鎖。
引數:
rwlock:讀寫鎖指標。
返回值:
成功:0
失敗:非 0 錯誤碼
5)銷燬讀寫鎖
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
功能:
用於銷燬一個讀寫鎖,並釋放所有相關聯的資源(所謂的所有指的是由 pthread_rwlock_init() 自動申請的資源) 。
引數:
rwlock:讀寫鎖指標。
返回值:
成功:0
失敗:非 0 錯誤碼
讀寫鎖應用例項
下面是一個使用讀寫鎖來實現 4 個執行緒讀寫一段資料是例項。在此示例程式中,共建立了 4 個執行緒,其中兩個執行緒用來寫入資料,兩個執行緒用來讀取資料。當某個執行緒讀操作時,其他執行緒允許讀操作,卻不允許寫操作;當某個執行緒寫操作時,其它執行緒都不允許讀或寫操作。
示例程式碼如下:
- #include<stdio.h>
- #include<unistd.h>
- #include<pthread.h>
- pthread_rwlock_t rwlock; //讀寫鎖
- int num = 1;
- //讀操作,其他執行緒允許讀操作,卻不允許寫操作
- void *fun1(void *arg)
- {
- while(1)
- {
- pthread_rwlock_rdlock(&rwlock);
- printf("read num first===%d\n",num);
- pthread_rwlock_unlock(&rwlock);
- sleep(1);
- }
- }
- //讀操作,其他執行緒允許讀操作,卻不允許寫操作
- void *fun2(void *arg)
- {
- while(1)
- {
- pthread_rwlock_rdlock(&rwlock);
- printf("read num second===%d\n",num);
- pthread_rwlock_unlock(&rwlock);
- sleep(2);
- }
- }
- //寫操作,其它執行緒都不允許讀或寫操作
- void *fun3(void *arg)
- {
- while(1)
- {
- pthread_rwlock_wrlock(&rwlock);
- num++;
- printf("write thread first\n");
- pthread_rwlock_unlock(&rwlock);
- sleep(2);
- }
- }
- //寫操作,其它執行緒都不允許讀或寫操作
- void *fun4(void *arg)
- {
- while(1)
- {
- pthread_rwlock_wrlock(&rwlock);
- num++;
- printf("write thread second\n");
- pthread_rwlock_unlock(&rwlock);
- sleep(1);
- }
- }
- int main()
- {
- pthread_t ptd1, ptd2, ptd3, ptd4;
- pthread_rwlock_init(&rwlock, NULL);//初始化一個讀寫鎖
- //建立執行緒
- pthread_create(&ptd1, NULL, fun1, NULL);
- pthread_create(&ptd2, NULL, fun2, NULL);
- pthread_create(&ptd3, NULL, fun3, NULL);
- pthread_create(&ptd4, NULL, fun4, NULL);
- //等待執行緒結束,回收其資源
- pthread_join(ptd1,NULL);
- pthread_join(ptd2,NULL);
- pthread_join(ptd3,NULL);
- pthread_join(ptd4,NULL);
- pthread_rwlock_destroy(&rwlock);//銷燬讀寫鎖
- return 0;
- }
執行結果如下: