2026-03-25 17:27:43 +00:00
|
|
|
|
---
|
|
|
|
|
|
title: "[学习笔记]经典的读者、写者问题"
|
|
|
|
|
|
pubDate: 2025-06-13
|
|
|
|
|
|
description: '关于读写者算法的学习笔记'
|
|
|
|
|
|
author: "三叶"
|
|
|
|
|
|
image:
|
|
|
|
|
|
url: "https://files.seeusercontent.com/2026/03/25/cd8O/pasted-image-1774458552123.webp"
|
|
|
|
|
|
alt: "img"
|
|
|
|
|
|
tags: ["算法", "操作系统", "学习笔记"]
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 首先我们来理解下读者、写者的概念
|
|
|
|
|
|
|
|
|
|
|
|
计算机中有**读者**、**写者**两个并发进程,共享一个文件。
|
|
|
|
|
|
|
|
|
|
|
|
允许两个及以上的读进程同时访问共享数据,但不允许某个写进程和其他进程同时操作。
|
|
|
|
|
|
|
|
|
|
|
|
**因此要求:**
|
|
|
|
|
|
|
|
|
|
|
|
1️⃣ 允许多个读者可以同时对文件执行读操作
|
|
|
|
|
|
|
|
|
|
|
|
2️⃣ 只允许一个写者往文件中写信息
|
|
|
|
|
|
|
|
|
|
|
|
3️⃣ 任一写者在完成写操作之前不允许其他读者或写者工作
|
|
|
|
|
|
|
|
|
|
|
|
4️⃣ 写者执行写操作前,应让已有的读者和写者全部退出
|
|
|
|
|
|
|
|
|
|
|
|
即**读进程和读进程可并发,而写进程和读/写进程均互斥**。
|
|
|
|
|
|
|
|
|
|
|
|
## 该如何实现呢?
|
|
|
|
|
|
|
|
|
|
|
|
## 其中一种思路
|
|
|
|
|
|
|
|
|
|
|
|
我们很容易想到,可以给缓冲区上一个读写锁,当写进程开始往缓冲区中写数据时上锁,不允许其他进程访问缓冲区。
|
|
|
|
|
|
|
|
|
|
|
|
那如何实现读进程并行呢?
|
|
|
|
|
|
|
|
|
|
|
|
我们设置一个信号量`count`,每一个读进程开始读数据时count++,当它结束读数据后count--。
|
|
|
|
|
|
|
|
|
|
|
|
那事情就简单了,第一个读进程开始读数据时上锁,最后一个读数据结束时解锁。
|
|
|
|
|
|
|
|
|
|
|
|
那我们上代码。
|
|
|
|
|
|
|
|
|
|
|
|
semaphore rw = 1; // 读写锁,用于实现共享进程的互斥访问 int count = 0; // 记录有几个读进程在访问文件 semaphore mutex = 1; // count的读写锁,防止同时有两个读进程访问count导致其中一个死锁 <div></div> writer () { while(1){ P(rw); // 给缓冲区上锁,进行P操作, rw-- 写文件; V(rw); // 解锁,进行V操作,rw++ } } <div></div> 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); } }
|
|
|
|
|
|
|
2026-03-26 13:50:23 +00:00
|
|
|
|
```c++
|
2026-03-25 17:27:43 +00:00
|
|
|
|
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,会导致写进程始终处于忙等,最终导致饥饿。**这种算法会导致读进程具有最高优先级。**
|
|
|
|
|
|
|
|
|
|
|
|
### 另一种读写公平的思路
|
|
|
|
|
|
|
|
|
|
|
|
既然读进程队列不空就无法写,那我们不如这么办:
|
|
|
|
|
|
|
|
|
|
|
|
当有写进程请求缓冲区资源时,**先将在此写进程之后来的所有读进程阻塞**,不进入读进程队列。
|
|
|
|
|
|
|
|
|
|
|
|
待读进程队列消耗完,写进程往缓冲区中写入数据结束之后,再将后来的读进程放进来。
|
|
|
|
|
|
|
|
|
|
|
|
那么我们上代码:
|
|
|
|
|
|
|
2026-03-26 13:50:23 +00:00
|
|
|
|
```c++
|
2026-03-25 17:27:43 +00:00
|
|
|
|
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`之后,大大提升了写进程的优先级,实现了**先来先服务**。
|