读者写者问题-读者优先,读写公平,写者优先
本文发表于我的个人博客:进程同步中的读者写者问题
问题描述
有读者和写者两组并发进程,共享一个文档,因此要求:
- 允许多个读者同时读
- 只允许一个写者往文件中写信息
- 任意一个写者在完成写操作之前不允许其他读者或写者工作
- 写者写操作前,应该让已有的读者和写者全部退出
读者优先(写者可能饿死)
这部分比较简单,一般课件上都会有
使用count
来记录读者的数量,mutex
保护count
变量, rw
保证读者写者互斥访问文件
int count = 0;
Semaphore mutex = 1; // count 变量保护
Semaphore rw = 1; // 文件保护
void writer(){
while(true){
P(rw);
// writing 开始写
V(rw); // 写完了释放临界区
}
}
void reader(){
while(true){
P(mutex);
if (count == 0) // 如果是第一个读者进入,那么开始占用文件
P(rw);
count++;
V(mutex);
// reading 开始读
P(mutex);
count--;
if (count == 0) // 如果是最后一个读者离开,释放文件占用
V(rw);
V(mutex);
}
}
由此可见,上面这个算法中,若源源不断地有读者进来,count
会一直不为0,导致写者没有办法进入临界区,一直被P(rw)
阻塞在外面卡住了。
读写公平
希望能读者、写者先来后到按照顺序进行,在上面的基础上,当有写进程请求访问,应当禁止后续的读进程的请求,等到已在共享文件的读进程执行完毕,立即让写进程执行。
在没有写者的情况下,才会让读者再次运行。
为此,在读者优先的基础上,增加一个信号量q
的PV操作,对读者、写者进行约束。
其中,P(q)
相当于开始排队,V(q)
相当于排队的人走了唤醒下一个队列首的人。
int count = 0;
Semaphore mutex = 1; // count 变量保护
Semaphore rw = 1; // 文件保护
Semaphore q = 1; // 使得writer和reader在同一个队列排队
void writer(){
while(true){
P(q); // 无写进程时请求进入
P(rw);
// writing 正在写
V(rw); // 写完了释放临界区
P(q); // 恢复对共享文件的访问
}
}
void reader(){
while(true){
P(q); // 这一步使得reader有机会让给writer写
P(mutex);
if (count == 0) // 如果是第一个读者进入,那么开始占用文件
P(rw);
count++;
V(mutex);
V(q); // 这一步唤醒被阻塞在q队列的第一个进程
// reading 正在读
P(mutex);
count--;
if (count == 0) // 如果是最后一个读者离开,释放文件占用
V(rw);
V(mutex);
}
}
写者优先问题
-
由于读-写、写-写互斥,使用信号量
file
控制临界区访问- 一群读者在读取时若有写者进来,会在队列占有
quemutex
,等待最后一个正在读的人离开释放file
,然后立刻开始写
- 一群读者在读取时若有写者进来,会在队列占有
-
使用一个
readcount
整型变量计数器,记录读者数量,用信号量rdcntmutex
控制该变量的访问;writecount
记录写者数量,需要信号量wrtcntmutex
控制该变量访问 -
为了实现写者优先,那么不能按照读者写者来的先后顺序排队,故设置两个队列,优先队列
quemutex
和读者等待队列readEntry
。- 当一个写者来时占用优先队列
quemutex
,一直占有到最后一个写者离开才能释放 - 当一个读者来时放到
readEntry
排队,若此时前面没有其他读者,也没有写者,它才可以进到quemutex
"就绪区",quemutex
仅允许一个读者存在 - 后续所有的读进程要统一放到
readEntry
等待。 - 直到所有的写进程都写完了,才会唤醒
quemutex
中的唯一读者,该读者完成任务后,才会唤醒readEntry
的第一个读者进入quemutex
(即quemutex
队列长度最多为2)
- 当一个写者来时占用优先队列
int readcount = 0, writecount = 0;
Semaphore file = 1, rdcntmutex = 1, wrtcntmutex = 1;
Semaphore quemutex = 1, readEntry = 1;
void Reader(){
P(readEntry);
P(quemutex); // 若有写者占用que,后续读者会被卡在外面
P(rdcntmutex);
if (readcount == 0)
P(file); // 若是第一个读者进来,则占据临界区阻止写进程写
readcount++;
V(rdcntmutex);
V(quemutex);
V(readEntry);
// 读操作不用互斥保护
// reading operation
// 读完了,读者走人
P(rdcntmutex);
if (readcount == 1)
V(file); // 若是最后一个读者离开,则释放写进程的阻塞,允许写
readcount--;
V(rdcntmutex);
}
void Writer(){
P(wrtcntmutex);
if (writecount == 0)
P(quemutex); // 一旦写者进入,则占用该优先队列
writecount++;
V(wrtcntmutex);
P(file); // 等待现存读者读完唤醒阻塞的写者
// writing, 写进程需要互斥保护
V(file);
P(wrtcntmutex);
if (writecount == 1)
V(quemutex);// 若源源不断有写者进入,writecount不会为0,也不会释放
writecount--; // 当最后一个写者离开,才会唤醒等在que的一个读者
V(wrtcntmutex);
}