【Liux系統程式設計】讀寫鎖
讀寫鎖
讀寫鎖從字面意思我們也可以知道,它由「讀鎖」和「寫鎖」兩部分構成,如果只讀取共享資源用「讀鎖」加鎖,如果要修改共享資源則用「寫鎖」加鎖。
所以,讀寫鎖適用於能明確區分讀操作和寫操作的場景。
讀寫鎖的工作原理是:
- 當「寫鎖」沒有被執行緒持有時,多個執行緒能夠併發地持有讀鎖,這大大提高了共享資源的訪問效率,因為「讀鎖」是用於讀取共享資源的場景,所以多個執行緒同時持有讀鎖也不會破壞共享資源的資料。
- 但是,一旦「寫鎖」被執行緒持有後,讀執行緒的獲取讀鎖的操作會被阻塞,而且其他寫執行緒的獲取寫鎖的操作也會被阻塞。
所以說,寫鎖是獨佔鎖,因為任何時刻只能有一個執行緒持有寫鎖,類似互斥鎖和自旋鎖,而讀鎖是共享鎖,因為讀鎖可以被多個執行緒同時持有。
知道了讀寫鎖的工作原理後,我們可以發現,讀寫鎖在讀多寫少的場景,能發揮出優勢。
另外,根據實現的不同,讀寫鎖可以分為「讀優先鎖」和「寫優先鎖」。
讀優先鎖期望的是,讀鎖能被更多的執行緒持有,以便提高讀執行緒的併發性,它的工作方式是:當讀執行緒 A 先持有了讀鎖,寫執行緒 B 在獲取寫鎖的時候,會被阻塞,並且在阻塞過程中,後續來的讀執行緒 C 仍然可以成功獲取讀鎖,最後直到讀執行緒 A 和 C 釋放讀鎖後,寫執行緒 B 才可以成功獲取讀鎖。如下圖:
而寫優先鎖是優先服務寫執行緒,其工作方式是:當讀執行緒 A 先持有了讀鎖,寫執行緒 B 在獲取寫鎖的時候,會被阻塞,並且在阻塞過程中,後續來的讀執行緒 C 獲取讀鎖時會失敗,於是讀執行緒 C 將被阻塞在獲取讀鎖的操作,這樣只要讀執行緒 A 釋放讀鎖後,寫執行緒 B 就可以成功獲取讀鎖。如下圖:
歸納總結:
1. 函式原型:
#include <pthread.h> pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); // 初始化讀寫鎖 pthread_rwlock_destroy(pthread_rwlock_t *rwlock); // 銷燬讀寫鎖
2. 函式原型:
#include <pthread.h> int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); //讀鎖 int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // 寫鎖 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // 解鎖
【 問題描述】程式 trainticket 中,有 100 個執行緒,其中 90 個執行緒是查餘票數量的,只有 10 個執行緒搶票,每個執行緒一次買 10 張票。初始狀態下一共有 1000 張票。因此執行完畢後,還會剩下 900 張票。
程式 trainticket 在執行的時候需要傳入引數,即:
引數 0:表示不加任何鎖
引數 1:表示使用讀寫鎖
引數 2:表示使用互斥量
程式碼實現:
1 #include <unistd.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <pthread.h> 6 7 struct Ticket 8 { 9 int remain; // 餘票數,初始化為 1000 10 pthread_rwlock_t rwlock; // 讀寫鎖 11 pthread_mutex_t mlock; // 互斥鎖,主要是為了和讀寫鎖進行對比 12 }ticket; 13 14 // 通過命令列傳引數來取得這個值,用來控制到底使用哪一種鎖 15 // 0:不加鎖 1:加讀寫鎖 2:加互斥鎖 16 int lock = 0; 17 18 void* query(void* arg) //查票執行緒 19 { 20 int name = (int)arg; 21 sleep(rand() % 5 + 1); 22 if (lock == 1) 23 pthread_rwlock_rdlock(&ticket.rwlock); // 讀模式加鎖 24 else if (lock == 2) 25 pthread_mutex_lock(&ticket.mlock); 26 27 int remain = ticket.remain; 28 sleep(1); 29 printf("%03d query: %d\n", name, remain); 30 31 if (lock == 1) 32 pthread_rwlock_unlock(&ticket.rwlock); 33 else if (lock == 2) 34 pthread_mutex_unlock(&ticket.mlock); 35 36 return NULL; 37 } 38 39 40 void* buy(void* arg) // 搶票執行緒 41 { 42 int name = (int)arg; 43 44 if (lock == 1) 45 pthread_rwlock_wrlock(&ticket.rwlock); // 寫模式加鎖 46 else if (lock == 2) 47 pthread_mutex_lock(&ticket.mlock); 48 49 int remain = ticket.remain; 50 remain -= 10; // 一次買 10 張票 51 sleep(1); 52 ticket.remain = remain; 53 printf("%03d buy 10 tickets\n", name); 54 55 if (lock == 1) 56 pthread_rwlock_unlock(&ticket.rwlock); 57 else if (lock == 2) 58 pthread_mutex_unlock(&ticket.mlock); 59 60 sleep(rand() % 5 + 2); 61 return NULL; 62 } 63 64 int main(int argc, char* argv[]) 65 { 66 lock = 0; 67 if (argc >= 2) 68 lock = atoi(argv[1]); 69 70 int names[100]; 71 pthread_t tid[100]; 72 73 int i; 74 for (i = 0; i < 100; ++i) 75 names[i] = i; 76 ticket.remain = 1000; 77 printf("remain ticket = %d\n", ticket.remain); 78 79 pthread_rwlock_init(&ticket.rwlock, NULL); 80 pthread_mutex_init(&ticket.mlock, NULL); 81 82 for (i = 0; i < 100; ++i) { 83 if (i % 10 == 0) 84 pthread_create(&tid[i], NULL, buy, (void*)names[i]); 85 else 86 pthread_create(&tid[i], NULL, query, (void*)names[i]); 87 } 88 89 for (i = 0; i < 100; ++i) 90 pthread_join(tid[i], NULL); 91 pthread_rwlock_destroy(&ticket.rwlock); 92 pthread_mutex_destroy(&ticket.mlock); 93 printf("remain ticket = %d\n", ticket.remain); 94 return 0; 95 }