1. 程式人生 > 其它 >【Liux系統程式設計】讀寫鎖

【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 }

 

參考資料

1. 面試官:你說說互斥鎖、自旋鎖、讀寫鎖、悲觀鎖、樂觀鎖的應用場景