操作系统经典PV操作——读者-写者问题的公平性实现
一、引言
在经典的同步互斥问题中,读者-写者是一个经典的用于模拟多进程对共享资源的访问的模型。在我的这篇博客里,详细介绍了一种最简单的实现方式:
https://blog.csdn.net/Wu_Deng_Sheng/article/details/149916391?spm=1001.2014.3001.5502
但这种方式有一个严重的问题,即“写者饥饿”。当读者进程源源不断地进来时,count始终大于0,那么写者进程便会无限等待下去,最终导致写者饥饿。而本文旨在介绍一种公平的算法,来解决这一问题。
二、问题解决
想要实现公平性,那么在读者写者想要申请资源的第一步,便要进入同一竞争队列,避免某一类进程长期抢占资源,下面我将来详细展开说明。
① 第一步:有几类进程?
与基础解法一致,系统中仅存在两类进程,且同类进程操作逻辑完全相同:
1、读者进程:仅读取共享资源,不修改数据,支持多个读者进程进程并发读取文件。
2、写者进程:修改共享资源,需独占访问,执行时不允许其他读写进程访问文件。
② 第二步:需要完成哪些限制——信号量设置
在基础解法的信号量基础上,新增一个关键信号量w,用于实现读者与写者的公平竞争入口。
int 变量
count = 0 记录读取数据的读者数量;
互斥信号量:
rw = 1 读者与写者互斥访问文件
mutex = 1 互斥访问修改count变量
w = 1
公平性实现核心,读者与写者在申请资源前,需要先竞争该信号量,即第一个大锁,确保两类进程进入同一等待队列,避免某一类长期霸占资源。
③ 他们分别要完成哪些任务?
对写者来说:
进入系统 —> 全局等待队列 —> 确定是否可以修改数据 —> 修改数据 —> 离开
1、独占写
2、不饥饿(先申请w信号量,防止后续写者插队)
while (1) { // 1. 第一步:申请公平竞争信号量w,进入全局等待队列// 若此时有读者或写者持有w,当前写者阻塞,避免被后续读者插队P(w); // 2. 第二步:申请互斥锁(阻止读者和其他写者)P(rw); // 3. 非临界区写操作; // 4. 第三步:释放互斥锁V(rw); // 5. 第四步:释放公平竞争信号量w,让等待队列中的下一个进程进入V(w);
}
对读者来说:
进入系统 —> 全局等待队列 —> 确定是否可以读数据 —> 读数据 —> 离开
1、并发读
2、不抢占写者(先申请w信号量,避免后续写者插队到写者之前)
while (1) { // 1. 第一步:申请公平竞争信号量w,进入全局等待队列// 若此时有写者或其他读者持有w,当前读者阻塞,避免持续抢占P(w); // 2. 第二步:申请count互斥锁P(mutex); // 3. 临界区:读者数量加1count++; // 4. 若是第一个读者,申请rw信号量,阻止写者访问// 第一个读者持有rw后;后续读者无需再申请rw(支持并发读)if (count == 1) {P(rw); } // 5. 释count修改权限V(mutex); // 6. 释放w信号量,让等待队列中的下一个进程(读者或写者)获得竞争权V(w); // 7. 非临界区读操作; // 8. 第三步:再次申请count互斥锁P(mutex); // 9. 临界区:读者数量减1count--; // 10. 若是最后一个读者,释放rw互斥锁,允许写者访问if (count == 0) {V(rw); } // 11. 释放count修改权限V(mutex);
}
三、总结
该方法通过引入一个大锁w,来实现读者-写者的公平调度,核心思想是在最开始申请时,就让每一个进程进入一个排队序列,让后续读者无法跨过先提交申请的写者进程从而实现读写公平。