读写锁
读写锁从字面意思我们也可以知道,它由「读锁」和「写锁」两部分构成,如果只读取共享资源用「读锁」加锁,如果要修改共享资源则用「写锁」加锁。
所以,读写锁适用于能明确区分读操作和写操作的场景。
读写锁的工作原理是:
- 当「写锁」没有被线程持有时,多个线程能够并发地持有读锁,这大大提高了共享资源的访问效率,因为「读锁」是用于读取共享资源的场景,所以多个线程同时持有读锁也不会破坏共享资源的数据。
- 但是,一旦「写锁」被线程持有后,读线程的获取读锁的操作会被阻塞,而且其他写线程的获取写锁的操作也会被阻塞。
所以说,写锁是独占锁,因为任何时刻只能有一个线程持有写锁,类似互斥锁和自旋锁,而读锁是共享锁,因为读锁可以被多个线程同时持有。
知道了读写锁的工作原理后,我们可以发现,读写锁在读多写少的场景,能发挥出优势。
另外,根据实现的不同,读写锁可以分为「读优先锁」和「写优先锁」。
读优先锁期望的是,读锁能被更多的线程持有,以便提高读线程的并发性,它的工作方式是:当读线程 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); // 销毁读写锁
#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 }