操作系统PV问题——一个略为复杂的读者写者问题
昨天在西班牙某技术论坛上看到一个有意思的PV操作题目,和传统的读写操作不一样,他这里多了一个缓冲区。想着之前自己操作系统学的不太扎实,于是就拿着练练手了。
原题目是这么说的:
Tenemos n 1 n_1 n1 procesos A i A_i Ai que realizan operaciones de escritura ( 1 < = i < = n 1 1<=i<=n_1 1<=i<=n1) y n 2 n_2 n2 procesos B i B_i Bi que realizan operaciones de lectura ( 1 < = i < = n 2 1<=i<=n_2 1<=i<=n2), donde los A i A_i Ai están constantemente enviando mensajes a los B i B_i Bi a través de m buffers. La comunicación de mensajes entre ellos sigue las siguientes tres reglas:
-
Cada proceso emisor A i A_i Ai envía un mensaje a la vez y llena una memoria de búfer;
-
Cada proceso receptor B i B_i Bi recibe un mensaje de A i A_i Ai sólo una vez;
-
Si todas las m m m memorias de búfer están ocupadas, el proceso emisor espera; y si todas ellas están vacías, el proceso receptor espera.
Escriba un pseudocódigo para el algoritmo anterior utilizando operaciones P/V e impleméntelo en cualquier lenguaje de programación (C/C++/Java/Python).
翻译成人话就是:有 n 1 n_1 n1个进程 A i A_i Ai通过 m m m个缓冲区向 n 2 n_2 n2个进程 B i B_i Bi发送 m s g msg msg条数据,然后用P/V操作设计一个算法,使得A的写和B的读操作互斥:A写满缓冲区时A挂起,B读空缓冲区时B也得等待。
那么首先来分析题目,题目中出现了一个缓冲区,是临界资源。是个人都知道这玩意必须要用一个信号量保护起来以免冲突,于是就定义一个信号量mutex,刚开始是没有使用的因此初值为1。缓冲区内部又分为n个块,读取和写入每一个块的时候又可能会冲突,所以对于写者得有一个判定是否还有空缓冲区块的信号量empty[0…n_1],并且初始状态下写进程没写入任何数据,缓冲区完全为空,故将empty数组的初值全部置为m。对于每个读者进程,需要有一个表示剩余可读量的信号量readable[i],并置数组初值为0,表示初始状态下写进程没写入数据时没有数据可以从缓冲区读入。
那么将上述操作转换为伪代码就是:
// 写者进程
void Escribir() {
while(1) {
// for every write process, be ready to write to buffer
for(int i = 0; i < n1; i++) P(empty[i]);
// take possession of buffer
P(mutex);
消息放入缓冲区;
// release buffer
V(mutex)
// for every read process, notify that it can read a piece of message
for(int i = 0; i < n2; i++) V(readable[i]);
}
}
// 读者进程
void Leer() {
while(1) {
for(int i = 0; i < n2; i++) P(readable[i]); // read a piece of message
P(mutex); // take possession of buffer
读取缓冲区;
// release buffer
V(mutex);
for(int i = 0; i < n1; i++) V(empty[i]); // now a block in buffer has been freed
}
}
下面我用Java来实现,并且使用Java的线程类Thread来模拟进程(但并不等同于进程!!这点一定要注意)。假设有 m s g msg msg条消息要传送,上述伪代码中,消息存入缓冲区可以模拟为:设未发送消息量为 r e m rem rem,初值为 m s g msg msg,每执行while循环体内一次该操作,就将 r e m rem rem自减1,最后输出“已向缓冲区写入一条数据”以及 r e m rem rem的值,并且在每次while循环体开头处检查所有消息是否传送完毕,如果传送完毕就退出循环,结束写者线程A。从缓冲区中读取消息的模拟方法和上述写者进程的操作类似。
Java中信号量是用Semaphore类来实现的,P/V操作则分别对应于信号量对象的成员函数acquire()和release()。
最终代码如下:
import java.util.*;
import java.util.concurrent.*;
public class Main {
private int n1, n2, m, msg;
private Semaphore mutex = new Semaphore(1);
private Semaphore[] readable, empty;
class Escribir implements Runnable {
@Override
public void run() {
int rem = msg;
try {
while(true) {
if(rem <= 0) break;
for(int i = 0; i < n1; i++) empty[i].acquire();
mutex.acquire();
System.out.println("Wrote 1 byte msg to buffer, now remains: " + (--rem));
mutex.release();
for(int i = 0; i < n2; i++) readable[i].release();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Leer implements Runnable {
@Override
public void run() {
int cnt = 0;
try {
while(true) {
if(cnt >= msg) break;
for(int i = 0; i < n2; i++) readable[i].acquire();
mutex.acquire();
System.out.println("Read 1 byte msg from buffer, until now received: " + (++cnt));
mutex.release();
for(int i = 0; i < n1; i++) empty[i].release();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Main() {
Scanner sc = new Scanner(System.in);
System.out.println("How many readers:");
n1 = sc.nextInt();
System.out.println("How many writers:");
n2 = sc.nextInt();
System.out.println("How many blocks in buffer:");
m = sc.nextInt();
System.out.println("How many messages?");
msg = sc.nextInt();
empty = new Semaphore[n1+1];
readable = new Semaphore[n2+1];
Arrays.fill(empty, new Semaphore(m));
Arrays.fill(readable, new Semaphore(0));
Thread t1 = new Thread(new Escribir()), t2 = new Thread(new Leer());
t1.start();
t2.start();
}
public static void main(String[] args) {
new Main();
}
}
输入 n 1 n_1 n1 = 5, n 2 n_2 n2 = 4, m m m = 6, m s g msg msg = 12 时,运行效果如下: