## 21. 【解答】
本题是一个典型的生产者-消费者问题。A 和 B 既是生产者,又是消费者。首先分析题中的互斥关系,信箱 A 和信箱 B 作为生产者-消费者问题中的缓冲区,需要互斥访问,因此设置两个信号量 mutex_A 和 mutex_B。然后分析 A 和 B 的同步关系,A 有两个动作,分别是从信箱 A 中取出邮件和将新邮件放入信箱 B,因此设置信号量 Full_A 和 Empty_B,用来保证信箱 A 中有邮件可以取,信箱 B 中有空间可以放。B 的分析同理。因此本题的所有信号量设置如下:
semaphore Full_A = x; // Full_A 表示 A 的信箱中的邮件数量
<!-- page: 156 -->
```txt
semaphore Empty_A = M-x; // Empty_A 表示 A 的信箱中还可存放的邮件数量
semaphore Full_B = y; // Full_B 表示 B 的信箱中的邮件数量
semaphore Empty_B = N-y; // Empty_B 表示 B 的信箱中还可存放的邮件数量
semaphore mutex_A = 1; // mutex_A 用于 A 的信箱互斥
semaphore mutex_B = 1; // mutex_B 用于 B 的信箱互斥
互斥信号量的 PV 操作直接放置在对信箱操作的前后。在 A 取邮件之前需执行 P(Full_A)来检查信箱 A 中是否有邮件可取,取出邮件之后需执行 V(Empty_A),表示新增了一个邮件大小的空间;在 A 将邮件放入邮箱 B 之前需执行 P(Empty_B)来检车信箱 B 中是否有空间可以放,放完邮件之后需执行 V(Full_B),表示信箱 B 增加了一个邮件。B 的分析同理。
Cobegin
[tbl-36.md](tbl-36.md)
Coend
## 22.【解答】
对于这类问题:
首先,找出有可能需要互斥访问的变量,只有全局变量才可能需要互斥访问,也就是说,含全局变量的代码段才可能是临界区。而局部变量是不需要互斥访问的,就算不同的进程之间都有相同名字的局部变量,但对不同的进程来说,它们只认识属于自己的局部变量。
其次,有关互斥的难点不在于找出哪些变量有可能需要互斥访问,而要理解互斥是进程之间对某个变量的互斥,不能脱离具体的进程来谈某个变量需不需要互斥访问。前面说了全局变量有可能需要互斥访问,但也不一定,因为这要看进程对它的操作,因此,一组互斥关系需要指出是哪些进程对哪个变量的互斥。例如,考虑两种情况:第一种情况是,进程 1 和进程 2 需要对共享变量 C 互斥访问,进程 1 和进程 3 也需要对变量 C 互斥访问,这是两组不同的互斥关系,而不能简单地理解为对 C 的互斥;第二种情况是,进程 1、2、3 两两之间都需要对变量 C 互斥访问。这种情况与第一种情况的差别是,第一种情况允许进程 2、3 对变量 C 的访问是不互斥的。若题目给出的是第一种情况,而我们采用了第二种情况去解题,则可能有同学认为,我依然满足了题目的要求啊,进程 1、2 之间以及进程 1、3 之间对 C 的访问都是互斥的,但这样降低了并发度,因为第二种情况允许进程 2、3 对变量 C 的访问是不互斥的,这也是 PV 互斥的最大难点,就是在没有准确找准互斥关系的条件下,容易导致程序的并发度降低,从而扣分。
本题的分析如下。
<!-- page: 157 -->
首先,全局变量是 $x$、$y$、$z$,只有对这三个变量的访问才可能需要互斥。线程1涉及 $x$、$y$ 的访问(只读),线程2涉及 $y$、$z$ 的访问(只读),线程3涉及 $y$、$z$ 的访问(读和写)。
其次,找互斥关系,根据什么原则呢?答案是读者-写者原则。读和读之间不需要互斥,读和写之间、写和写之间需要互斥。因此,本题的互斥关系如下:第一对,thread1和thread3之间需要对变量 $y$ 的访问互斥;第二对,线程2和线程3之间需要对变量 $y$ 的访问互斥;第三对,线程2和线程3之间需要对变量 $z$ 访问的互斥。正确找出所有互斥关系后,接下来的操作就很简单。因为有三组互斥关系,所以定义三个互斥信号量,分别为 $\text{mutex\_y1} = 1$、$\text{mutex\_y2} = 1$ 和 $\text{mutex\_z} = 1$,然后分别将它们加到线程代码段的相应位置。例如,在线程1的 $w = \text{add}(x, y)$ 和线程3的 $y = \text{add}(y, w)$ 上下用 $\text{mutex\_y1}$ 夹住;在线程2的 $w = \text{add}(y, z)$ 和线程3的 $z = \text{add}(z, w)$ 上下用 $\text{mutex\_y2}$ 夹住;在线程2的 $w = \text{add}(y, z)$ 和线程3的 $y = \text{add}(y, w)$ 上下用 $\text{mutex\_z}$ 夹住。
## 24.【解答】
本题是一个典型的利用信号量实现前驱关系的同步问题。首先画出各个操作之间的执行顺序图,可以看出,A、B、D 的执行不需要任何前提条件。执行完 A 和 B 之后才能执行 C,存在两对同步关系:A→C 和 B→C,设置两个同步变量 SAC = 0 和 SBC = 0,完成 A 和 B 之后分别执行 V(SAC) 和 V(SBC),表示 A 或 B 已完成;执行 C 之前需要执行 P(SAC) 和 P(SBC),检查 A 和 B 是否完成。执行完 C 和 D 之后才能执行 E,也存在两对同步关系:C→E 和 D→E,因此再设置两个同步变量 SCE = 0 和 SDE = 0,完成 C 和 D 之后分别执行 V(SCE) 和 V(SDE),表示 C 或 D 已完成;执行 E 之前需要执行 P(SCE) 和 P(SDE),检查 C 和 D 是否完成。
semaphore SAC = 0; //控制 A 和 C 的执行顺序
semaphore SBC = 0; //控制 B 和 C 的执行顺序
semaphore SCE = 0; //控制 C 和 E 的执行顺序
semaphore SDE = 0; //控制 D 和 E 的执行顺序