经典进程同步问题——读者写者问题

经典进程同步问题——读者写者问题

1. 问题描述

一个数据文件或者记录可被多个进程共享,我们把只要求读文件的进程称为“Reader”进程,其他进程则称为“Writer”进程。允许多个进程同时读一个共享对象,因为读操作不会使数据文件混乱。但是不允许一个Writer进程和其他的Reader进程或Writer进程同时访问共享对象(因为这种访问会引起数据的混乱)。

也就是读者-写者问题要求:

  1. 允许多个读者同时执行读操作;
  2. 不允许读者、写者同时操作;
  3. 不允许多个写者同时操作。

2. 问题分析

我们按照准备访问共享对象的进程种类来进行问题的分析:

如果Reader进程准备访问共享对象,当前系统中分为以下几种情况:

1)无Reader、Writer,这个新Reader可以读;

2)有Writer等,但有其它Reader正在读,则新Reader也可以读;

3)有Writer写,新Reader等待。

如果Writer进程准备访问共享对象,当前系统中分为以下几种情况:

1)无Reader、Writer,新Writer可以写;

2)有Reader,新Writer等待;

3)有其它Writer,新Writer等待。

3. 信号量设置

  • 设置一个整型变量readcount表示正在读的进程数目,该变量是可被多个读进程访问的临界资源;

  • wmutex用于读者和写者、写者和写者进程之间的互斥;

  • rmutex用于对readcount这个临界资源的互斥访问。

4. 使用记录型信号量解决该问题——Java实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package ReaderWriter;

import java.util.concurrent.Semaphore;

/**
* Created by mzYan on 2020-01-27 19:30
*/
public class ReaderWriterTest1 {

static Semaphore rMutex = new Semaphore(1);
static Semaphore wMutex = new Semaphore(1);
static int readCount = 0;

// 读者
static class Reader extends Thread {
Reader(String name) {
super.setName(name);
}

@Override
public void run() {
while (true) {
try {
//操作readCount,需要先进入临界区
rMutex.acquire();
//判断在当前时刻,该读进程是否是系统中唯一的读者
if(readCount == 0){
wMutex.acquire();
}
//系统中的读者数量加1
readCount ++;
rMutex.release();
System.out.println("读者【" + getName() + "】在执行读操作,当前读者数:【" + readCount + "】");
Thread.sleep(5000);

//操作readCount,需要先进入临界区
rMutex.acquire();
readCount --;
//如果该读者是否是系统中最后离开的,则需要唤醒写者
if(readCount == 0){
wMutex.release();
}
rMutex.release();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

// 写者
static class Writer extends Thread {
Writer(String name) {
super.setName(name);
}

@Override
public void run() {
while (true) {
try {
//判断进入临界区
wMutex.acquire();
System.out.println("写者【" + getName() + "】执行了写操作");
Thread.sleep(1000);
wMutex.release();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public static void main(String[] args) {
Reader r1 = new Reader("r1");
Reader r2 = new Reader("r2");
Reader r3 = new Reader("r3");

Writer w1 = new Writer("w1");
Writer w2 = new Writer("w2");

r1.start();
r2.start();
r3.start();
w1.start();
w2.start();
}
}

对于读进程中的if(readcount == 0) p(wmutex),这是因为读者和写者之间的关系决定的,因为读者到达且为当前时刻t1系统中的第一个读者,所以需要让写进程无法进入临界区。这里,还有一个精妙的设计,就是如果在t1时刻,已经有写者在操作共享对象,此时第一个读者来,去申请wmutex信号量,必定会因为资源不足而阻塞,这里通过一个wmutex来控制读者和写者的同步,可以说设计的非常精妙了。