2.3.8【2015 统考真题】

2.3.8【2015 统考真题】
2.3.8【2015 统考真题】
好的,我们来详细解析这道2015年的考研真题。这道题是一个非常巧妙的“生产者-消费者”模型的变体,它将两个进程设置为互为生产者和消费者的角色,深刻考察了对模型本质的理解。


第一步:重述题目原文

【2015 统考真题】有 A, B 两人通过信箱进行辩论,每个人都从自己的信箱中取得对方的问题。将答案和向对方提出的新问题组成一个邮件放入对方的信箱中。假设 A 的信箱最多放 M 个邮件,B 的信箱最多放 N 个邮件。初始时 A 的信箱中有 x 个邮件 (0 < x < M),B 的信箱中有 y 个邮件 (0 < y < N)。辩论者每取出一个邮件,邮件数减 1。

当信箱不为空时,辩论者才能从信箱中取邮件,否则等待。当信箱不满时,辩论者才能将新邮件放入信箱,否则等待。请添加必要的信号量和 P, V 操作,以实现上述过程的同步。要求写出完整的过程,并说明信号量的含义和赋初值。


第二步:知识体系是什么?

这道题的本质是 两个耦合的、独立的生产者-消费者系统

识别双重角色

进程 A 的行为是:从自己的信箱 A 中 消费 邮件,然后向对方的信箱 B 中 生产 邮件。进程 B 的行为是:从自己的信箱 B 中 消费 邮件,然后向对方的信箱 A 中 生产 邮件。所以,A 和 B 互为生产者和消费者。A 是信箱 B 的生产者和信箱 A 的消费者;B 是信箱 A 的生产者和信箱 B 的消费者。

识别两个独立的缓冲区

信箱 A 是一个独立的共享缓冲区,容量为 M。信箱 B 是另一个独立的共享缓冲区,容量为 N。这两个缓冲区是完全独立的资源,对其中一个的操作不应该影响另一个。

核心模型

既然有两个独立的缓冲区,我们就需要为每一个缓冲区都配置一套完整的、标准的生产者-消费者解决方案。一套标准的解决方案包括:一个互斥锁 (
mutex
),一个计数空位的信号量 (
empty
),一个计数产品的信号量 (
full
)。


第三步:解题套路是什么?

套路一:分解系统,独立分析

不要把 A 和 B 的交互看成一个复杂的整体。而是分解成两个独立的子问题:

子问题1:如何管理信箱 A?子问题2:如何管理信箱 B?

套路二:为每个子问题配置“三件套”信号量

分析信箱 A (容量M, 初始x封)

互斥:任何时候只能有一个进程(A在取,或B在放)操作信箱A。 -> 定义
semaphore mutex_A = 1;
同步 (消费者A):A 要取信,必须等信箱A不为空。-> 定义
semaphore full_A = x;
(记录信箱A中的邮件数)同步 (生产者B):B 要放信,必须等信箱A不满。-> 定义
semaphore empty_A = M - x;
(记录信箱A中的空位数)

分析信箱 B (容量N, 初始y封)

互斥:任何时候只能有一个进程(B在取,或A在放)操作信箱B。 -> 定义
semaphore mutex_B = 1;
同步 (消费者B):B 要取信,必须等信箱B不为空。-> 定义
semaphore full_B = y;
(记录信箱B中的邮件数)同步 (生产者A):A 要放信,必须等信箱B不满。-> 定义
semaphore empty_B = N - y;
(记录信箱B中的空位数)

套路三:组合流程,安排P/V操作

进程 A 的完整流程

从自己信箱(A)取信 (作为消费者):

P(full_A)
// 等待信箱A有信
P(mutex_A)
// 锁定信箱A从信箱A取出一个邮件
V(mutex_A)
// 解锁信箱A
V(empty_A)
// 通知信箱A空位数+1
回答问题并提出新问题。向对方信箱(B)放信 (作为生产者):

P(empty_B)
// 等待信箱B有空位
P(mutex_B)
// 锁定信箱B将新邮件放入信箱B
V(mutex_B)
// 解锁信箱B
V(full_B)
// 通知信箱B邮件数+1

进程 B 的完整流程:与进程 A 完全对称,只需交换 A 和 B 即可。


第四步:为什么是这样?(深度剖析)

为什么需要两个互斥锁 (
mutex_A
,
mutex_B
)?

因为信箱 A 和信箱 B 是两个物理上和逻辑上都独立的资源。进程 A 正在操作信箱 B 时,不应该阻止进程 B 操作信箱 A。如果只用一个全局
mutex
,当 A 锁住它去访问 B 的信箱时,B 就无法访问自己的信箱 A,大大降低了并发性,甚至可能导致死锁。

为什么初始值不是0或M/N?

这是本题的一个关键细节。题目明确给出了“初始时A的信箱中有x个邮件,B有y个邮件”。信号量必须精确地反映资源的初始状态。所以
full
信号量直接对应初始的产品数
x

y
,而
empty
信号量则对应初始的空位数
M-x

N-y

P/V操作的配对逻辑

对于信箱A:
P(full_A)
(由A执行) 和
V(full_A)
(由B执行) 是一对,它们共同管理着
full_A
这个计数器。同样,
P(empty_A)
(由B执行) 和
V(empty_A)
(由A执行) 是一对。这个“交叉”关系正是“互为生产者-消费者”的体现。

死锁检查

再次应用黄金法则:“同步P在互斥P之前”。在进程A的代码中,
P(full_A)

P(mutex_A)
之前,
P(empty_B)

P(mutex_B)
之前。这确保了进程不会在持有锁(一种资源)的同时,去等待另一种资源(产品或空位),从而避免了死锁。


第五步:考试怎么解决?(实战指南)

识别模型:看到“两个人/进程”、“两个信箱/缓冲区”,立刻意识到这是“双缓冲区、互为生产者-消费者”模型。分解资源:在草稿纸上画两个圈,分别标注“信箱A”和“信箱B”。独立建模
在“信箱A”旁边写下它的三件套:
mutex_A
,
full_A
,
empty_A
。并根据题目的
M

x
标出初值:
1
,
x
,
M-x
。在“信箱B”旁边做同样的事:
mutex_B
,
full_B
,
empty_B
。标出初值:
1
,
y
,
N-y

构建一个进程的逻辑:只看进程A。
第一步:消费。从哪消费?信箱A。写下消费四步曲:
P(full_A)
,
P(mutex_A)
,
...
,
V(mutex_A)
,
V(empty_A)
第二步:生产。向哪生产?信箱B。写下生产四步曲:
P(empty_B)
,
P(mutex_B)
,
...
,
V(mutex_B)
,
V(full_B)

对称构建另一个进程:将进程A的代码复制一份,把所有的A换成B,B换成A,就得到了进程B的代码。最后整合:把信号量定义和
cobegin
框架写好,填入代码,完成。


【完整标准答案】

1. 信号量定义及含义

互斥信号量
mutex_A
:

含义: 用于保证进程 A 和 B 对信箱 A 的互斥访问。初值: 1。

互斥信号量
mutex_B
:

含义: 用于保证进程 A 和 B 对信箱 B 的互斥访问。初值: 1。

资源信号量
full_A
:

含义: 记录信箱 A 中当前邮件的数量。初值: x。

资源信号量
empty_A
:

含义: 记录信箱 A 中当前空闲位置的数量。初值: M – x。

资源信号量
full_B
:

含义: 记录信箱 B 中当前邮件的数量。初值: y。

资源信号量
empty_B
:

含义: 记录信箱 B 中当前空闲位置的数量。初值: N – y。

2. 进程同步与互斥的伪代码实现

// 信号量定义及初始化
semaphore mutex_A = 1, mutex_B = 1;
semaphore full_A = x, empty_A = M - x;
semaphore full_B = y, empty_B = N - y;

cobegin {
    // 辩论者 A 的进程
    process A {
        while(TRUE) {
            // 从自己的信箱A中取邮件
            P(full_A);              // 等待信箱A中有邮件
            P(mutex_A);             // 锁定信箱A
            // 从A的信箱中取出一个邮件;
            V(mutex_A);             // 解锁信箱A
            V(empty_A);             // 通知信箱A的空位数加一

            // 回答问题并提出一个新问题;

            // 将新邮件放入B的信箱
            P(empty_B);             // 等待信箱B中有空位
            P(mutex_B);             // 锁定信箱B
            // 将新邮件放入B的信箱;
            V(mutex_B);             // 解锁信箱B
            V(full_B);              // 通知信箱B的邮件数加一
        }
    }

    // 辩论者 B 的进程
    process B {
        while(TRUE) {
            // 从自己的信箱B中取邮件
            P(full_B);              // 等待信箱B中有邮件
            P(mutex_B);             // 锁定信箱B
            // 从B的信箱中取出一个邮件;
            V(mutex_B);             // 解锁信箱B
            V(empty_B);             // 通知信箱B的空位数加一
            
            // 回答问题并提出一个新问题;

            // 将新邮件放入A的信箱
            P(empty_A);             // 等待信箱A中有空位
            P(mutex_A);             // 锁定信箱A
            // 将新邮件放入A的信箱;
            V(mutex_A);             // 解锁信箱A
            V(full_A);              // 通知信箱A的邮件数加一
        }
    }
} coend
© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...