一、概念
一把读写锁具备三种状态:
- 读模式下加锁状态(读锁)
- 写模式下加锁转态(写锁)
- 不加锁状态
2. 读写锁特性:
- 读写锁是写模式加锁时,解锁前,所有对该锁加锁的线程都会阻塞。
- 读写锁是读模式加锁时,如果线程以读模式加锁会成功,如果线程以写模式加锁会阻塞
- 读写锁是读模式加锁时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程,那么读模式会读阻塞随后的读模式锁请求,优先满足写模式锁,读锁、写锁并行阻塞,写锁优先级高。
读写锁也叫共享_独占锁,当读写锁以读模式锁住时,它以共享模式锁住;当它以写模式锁住时,它是独占锁住的,写独占,读共享。
3. 读写锁的特性:
线程A加读锁成功, 又来了三个线程, 做读操作, 可以加锁成功
- 读共享 - 并行处理
线程A加写锁成功, 又来了三个线程, 做读操作, 三个线程阻塞
- 写独占
线程A加读锁成功, 又来了B线程加写锁阻塞, 又来了C线程加读锁阻塞
- 读写不能同时
- 写的优先级高
4. 读写锁场景:
线程A加写锁成功, 线程B请求读锁
- 线程B阻塞
线程A持有读锁, 线程B请求写锁
- 线程B阻塞
线程A拥有读锁, 线程B请求读锁
- 线程B加锁成功
线程A持有读锁, 然后线程B请求写锁, 然后线程C请求读锁
- B阻塞,c阻塞 - 写的优先级高
- A解锁,B线程加写锁成功,C继续阻塞
- B解锁,C加读锁成功
线程A持有写锁, 然后线程B请求读锁, 然后线程C请求写锁
- BC阻塞
- A解锁,C加写锁成功,B继续阻塞
- C解锁,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); 解锁
三、程序清单
1. 测试代码:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>int counter; //全局资源
pthread_rwlock_t rwlock;void *th_write(void *arg)
{int t;int i = (int)arg;while (1) {t = counter;usleep(1000);pthread_rwlock_wrlock(&rwlock);printf("=======write %d: %lu: counter = %d ++counter = %d\n", i, pthread_self(), t, ++counter);pthread_rwlock_unlock(&rwlock);usleep(5000);}return NULL;
}void *th_read(void *arg)
{int t;int i = (int)arg;while (1) {pthread_rwlock_rdlock(&rwlock);printf("---------------read %d: %lu: %d\n", i, pthread_self(), counter);pthread_rwlock_unlock(&rwlock);usleep(900);}return NULL;
}int main()
{int i;pthread_t tid[8];pthread_rwlock_init(&rwlock, NULL);for (i = 0; i < 3; ++i)pthread_create(&tid[i], NULL, th_write, (void*)i);for (i = 0; i < 5; ++i)pthread_create(&tid[i + 3], NULL, th_read, (void*)i);for (i = 0; i < 8; ++i)pthread_join(tid[i], NULL);pthread_rwlock_destroy(&rwlock);return 0;
}
输出结果:
互斥锁实现读写锁
#include<pthread.h>pthread_mutex_t rdLock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t wrLock = PTHREAD_MUTEX_INITIALIZER;
int readCnt = 0;//设置读锁调用次数的标志位//实现读锁(共享锁)
void rdLock()
{pthread_mutex_lock(&rdLock);readCnt++;if (readCnt == 1)//有人读,于是阻塞写锁pthread_mutex_lock(&wrLock);pthread_mutex_unlock(&rdLock);
}void rdUnlock()
{pthread_mutex_lock(&rdLock);readCnt--;if (readCnt == 0)//表示已没有人在读,释放写锁,可以写入了pthread_mutex_unlock(&wrLock);pthread_mutex_unlock(&rdLock);
}void wrLock()
{pthread_mutex_lock(&wrLcok);
}void wrUnlock()
{pthread_mutex_unlock(&wrLock);
}
一、实验项目
【问题描述】程序 trainticket 中,有 100 个线程,其中 90 个线程是查余票数量的,只有 10 个线程抢票,每个线程一次买 10 张票。初始状态下一共有 1000 张票。因此执行完毕后,还会剩下 900 张票。
程序 trainticket 在运行的时候需要传入参数,即:
- 参数 0:表示不加任何锁
- 参数 1:表示使用读写锁
- 参数 2:表示使用互斥量
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>struct Ticket
{int remain; // 余票数,初始化为 1000pthread_rwlock_t rwlock; // 读写锁pthread_mutex_t mlock; // 互斥锁,主要是为了和读写锁进行对比
}ticket;// 通过命令行传参数来取得这个值,用来控制到底使用哪一种锁
// 0:不加锁 1:加读写锁 2:加互斥锁
int lock = 0;void* query(void* arg) //查票线程
{int name = (int)arg;sleep(rand() % 5 + 1);if (lock == 1)pthread_rwlock_rdlock(&ticket.rwlock); // 读模式加锁else if (lock == 2)pthread_mutex_lock(&ticket.mlock);int remain = ticket.remain;sleep(1);printf("%03d query: %d\n", name, remain);if (lock == 1)pthread_rwlock_unlock(&ticket.rwlock);else if (lock == 2)pthread_mutex_unlock(&ticket.mlock);return NULL;
}void* buy(void* arg) // 抢票线程
{int name = (int)arg;if (lock == 1)pthread_rwlock_wrlock(&ticket.rwlock); // 写模式加锁else if (lock == 2)pthread_mutex_lock(&ticket.mlock);int remain = ticket.remain;remain -= 10; // 一次买 10 张票sleep(1);ticket.remain = remain;printf("%03d buy 10 tickets\n", name);if (lock == 1)pthread_rwlock_unlock(&ticket.rwlock);else if (lock == 2)pthread_mutex_unlock(&ticket.mlock);sleep(rand() % 5 + 2);return NULL;
}int main(int argc, char* argv[])
{lock = 0;if (argc >= 2) lock = atoi(argv[1]);int names[100];pthread_t tid[100];int i;for (i = 0; i < 100; ++i) names[i] = i;ticket.remain = 1000;printf("remain ticket = %d\n", ticket.remain);pthread_rwlock_init(&ticket.rwlock, NULL);pthread_mutex_init(&ticket.mlock, NULL);for (i = 0; i < 100; ++i) {if (i % 10 == 0)pthread_create(&tid[i], NULL, buy, (void*)names[i]);elsepthread_create(&tid[i], NULL, query, (void*)names[i]);}for (i = 0; i < 100; ++i) pthread_join(tid[i], NULL);pthread_rwlock_destroy(&ticket.rwlock);pthread_mutex_destroy(&ticket.mlock);printf("remain ticket = %d\n", ticket.remain);return 0;
}