date: 2020-04-20
读者-写者问题是非常著名的同步问题
reader 与 writer 的权限:
- reader :只读
- writer :读、写
reader 与 writer 在访问同一资源时应当满足:
- 多个 reader 可以共享资源,同时访问
- writer 访问时,其他 writer 和所有 reader 均不能访问
资源不能访问时即被阻塞,所以可以想象一个队列,各个进程排队执行操作。
变量定义
以下是在之后的代码中可能会用到的变量
int rdcnt = 0;
int wtcnt = 0;
semaphore rwmutex = 1;// 读写互斥(可以理解成 queue 锁,阻塞队列)
semaphore srcmutex = 1;// 资源互斥
semaphore rdcntmutex = 1;// 保护rdcnt
semaphore wtcntmutex = 1;// 保护wtcnt
基础代码
void writer() {
while (true) {
wait(srcmutex);
write()
signal(srcmutex);
}
}
void reader() {
while (true) {
wait(srcmutex);
read()
signal(srcmutex);
}
}
以上的代码实现了读写互斥的功能,保证操作的安全性,同时 reader 和 writer 之间是公平竞争。但是上述的代码使得 reader 之间不能共存,还有改进的空间。
优先级
在这里我们先对优先级建立一个共识:
- 在优先级较高的对象执行操作时,优先级较低的对象可以执行操作直到所有优先级较高的对象执行完其操作。
- 优先级较高的对象可以从优先级较低的对象手中夺得访问权,中断优先级较低的对象的操作。
条件一应当是必须满足的,而条件二则视情况而定。
由于我们不对读写的具体操作进行讨论,所以应当将其视为一个原子操作,因此本篇博客不考虑条件二。
读者优先
读者优先,其实就是允许 reader “插队”。在有 reader 访问资源时,其他 reader 可以直接访问,而 writer 需要等待所有 reader 全部执行完操作才能访问资源;同时 writer 访问完之后由其他的 writer 和 reader 争夺访问权。
实现读者优先只需要在基础代码上实现 reader 共享即可。
void writer() {
while (true) {
wait(srcmutex);
write()
signal(srcmutex);
}
}
void reader() {
while (true) {
// 第一个 reader 需要申请资源,其余 reader 之间访问
if (rdcnt == 0)
wait(srcmutex);
// reader 计数
wait(rdcntmutex);
rdcnt++;
signal(rdcntmutex);
read();
// 一个 reader 执行完操作
wait(rdcntmutex);
rdcnt--;
signal(rdcntmutex);
// 最后一个 reader 释放资源
if (rdcnt == 0)
signal(srcmutex);
}
}
reader - writer 公平竞争
要实现 reader 与 writer 公平竞争,需要确保两者的优先级是相当的,所以我们可以模拟排队时没有人插队的情况,即按照先来后到的顺序执行操作。
由于 reader 可以共享资源,所以 reader 天然就具有优先级上的优势,我们需要使用一个信号量来限制这种优势,使 writer 可以阻塞后来的 reader 。
void writer() {
while (true) {
// 读写互斥
wait(rwmutex);
// 进行写操作,如果 reader 尚未执行完毕则会一直等待,但是不会有新的 reader 开始读取
wait(srcmutex);// 申请资源
write()
signal(srcmutex);// 释放资源
// 释放令牌,允许其他线程执行操作
signal(rwmutex);
}
}
void reader() {
while (true) {
// 申请令牌,如果依旧有 writer 占有令牌,则需要等待之前的 reader 与 writer 完成操作
wait(rwmutex);
// 申请资源,防止 writer 申请到令牌时直接开始写资源
if (rdcnt == 0) wait(srcmutex);
// reader 计数
// 计数+1受到 rwmutex 保护,在 writer 占有令牌时不会有新的 reader 执行操作
wait(rdcntmutex);
rdcnt++;
signal(rdcntmutex);
// 释放令牌,允许其他进程争夺令牌
signal(rwmutex);
// 读取操作不受到 rwmutex 保护
// 所以在 writer 占有令牌时,尚未完成操作的 reader 可以继续操作
read()
// 在 writer 占有令牌时可以正常执行,直至 rdcnt 为 0,并将资源访问权转交给 writer
wait(rdcntmutex);
rdcnt--;
signal(rdcntmutex);
// 在所有 reader 读取完毕之后释放资源
if (rdcnt == 0) signal(srcmutex);
}
}
写者优先
有了之前的铺垫,我们可以很快想到,要实现写者优先,其实就是允许 writer “插队”,同时避免 reader “插队”。
基于这个思路,我们只需要再添加一个变量来记录 writer 的数量,在没有 writer 时释放令牌即可。
void writer() {
while (true) {
// 申请令牌
if (wtcnt == 0) wait(rwmutex);
// writer 计数
wait(wtcntmutex);
wtcnt++;
signal(wtcntmutex);
// 进行写操作
wait(srcmutex);// 申请资源
write();
signal(srcmutex);// 释放资源
// writer 计数
wait(wtcntmutex);
wtcnt--;
signal(wtcntmutex);
// 没有 writer 在队列中,释放令牌
if (wtcnt == 0) signal(rwmutex);
}
}
void reader() {
while (true) {
// 申请令牌
wait(rwmutex);
// 申请资源
if (rdcnt == 0) wait(srcmutex);
// reader 计数
wait(rdcntmutex);
rdcnt++;
signal(rdcntmutex);
signal(rwmutex);
read();
wait(rdcntmutex);
rdcnt--;
signal(rdcntmutex);
// 在所有 reader 执行完操作之后释放资源
if (rdcnt == 0) signal(srcmutex);
}
}