首先我们来理解下读者、写者的概念
计算机中有读者、写者两个并发进程,共享一个文件。
允许两个及以上的读进程同时访问共享数据,但不允许某个写进程和其他进程同时操作。
因此要求:
1️⃣ 允许多个读者可以同时对文件执行读操作
2️⃣ 只允许一个写者往文件中写信息
3️⃣ 任一写者在完成写操作之前不允许其他读者或写者工作
4️⃣ 写者执行写操作前,应让已有的读者和写者全部退出
即读进程和读进程可并发,而写进程和读/写进程均互斥。
该如何实现呢?
其中一种思路
我们很容易想到,可以给缓冲区上一个读写锁,当写进程开始往缓冲区中写数据时上锁,不允许其他进程访问缓冲区。
那如何实现读进程并行呢?
我们设置一个信号量count
,每一个读进程开始读数据时count++,当它结束读数据后count–。
那事情就简单了,第一个读进程开始读数据时上锁,最后一个读数据结束时解锁。
那我们上代码。
semaphore rw = 1; // 读写锁,用于实现共享进程的互斥访问
int count = 0; // 记录有几个读进程在访问文件
semaphore mutex = 1; // count的读写锁,防止同时有两个读进程访问count导致其中一个死锁
writer () {
while(1){
P(rw); // 给缓冲区上锁,进行P操作, rw--
写文件;
V(rw); // 解锁,进行V操作,rw++
}
}
reader () {
while(1){
P(mutex); // 给count上锁,防止有两个读进程同时读到count==0,导致死锁。
if (count == 0){
P(rw); // 如果count为0,则检查缓冲区是否上锁,若上锁则等待,若无锁则为缓冲区上锁。
}
count++;
V(mutex); // 解除count锁
读文件;
P(mutex); // 给count上锁,防止有两个进程同时读到count==0,导致rw+2产生异常
count--;
if (count == 0){
V(rw); // 如果count为0,则代表这个进程为最后一个读进程,解除缓冲区的锁。
}
V(mutex);
}
}
但是我们会发现这个思路有个问题。
如果一直有读进程进来怎么办?
如果一直有读进程申请读缓冲区(这在操作系统中很常见),那么count一直不为0,会导致写进程始终处于忙等,最终导致饥饿。这种算法会导致读进程具有最高优先级。
另一种读写公平的思路
既然读进程队列不空就无法写,那我们不如这么办:
当有写进程请求缓冲区资源时,先将在此写进程之后来的所有读进程阻塞,不进入读进程队列。
待读进程队列消耗完,写进程往缓冲区中写入数据结束之后,再将后来的读进程放进来。
那么我们上代码:
semaphore rw = 1;
int count = 0;
semaphore mutex = 1;
semephore w = 1; // 用于实现写优先
writer () {
while (1) {
P(w); // 写进程先给w锁上锁,上锁后后来的读进程都需等待这个写进程解锁。
P(rw); // 给缓冲区上锁
写文件;
V(rw);
V(w); // 解锁,允许读进程加入读队列。
}
}
reader () {
while (1) {
P(w); // 新的读进程加入时先检查w锁是否上锁,若已上锁则阻塞等待。若未上锁则先给它上锁
P(mutex);
if (count == 0)
P(rw);
count++;
V(mutex);
V(w); // 读进程加入队列之后,将w锁解锁,保证加入队列操作的原子性。
读文件;
P(mutex);
count--;
if (count == 0)
V(rw);
V(mutex);
}
}
加入了一个新的锁w
之后,大大提升了写进程的优先级,实现了先来先服务。