一、概念
一把读写锁具备三种状态:
- 读模式下加锁状态(读锁)
- 写模式下加锁转态(写锁)
- 不加锁状态
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; // 余票数,初始化为 1000
pthread_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]);
else
pthread_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;
}