Skip to content

2009年 408 操作系统 第 45 题

操作系统2009年综合题7分

题目

三个进程 P1、P2、P3 互斥使用一个包含 N(N > 0)个单元的缓冲区

  • P1:每次用 produce() 生成一个正整数,并用 put() 送入缓冲区某一空单元
  • P2:每次用 getodd() 从该缓冲区中取出一个奇数,并用 countodd() 统计奇数个数
  • P3:每次用 geteven() 从该缓冲区中取出一个偶数,并用 counteven() 统计偶数个数

请用信号量机制实现这三个进程的同步与互斥活动,并说明所定义信号量的含义。要求用伪代码描述。

解析

关系拆解

先把三个进程之间的关系列清楚:

关系类型涉及进程用什么信号量为什么
互斥三方共享缓冲区,一次只能一个进程操作mutex (init=1)put/getodd/geteven 都要改缓冲区状态
同步:缓冲区满 / 空P1 不能往满了的缓冲区放、P2/P3 不能从空缓冲区取empty (init=N)经典 PV 容量管理
同步:奇数到达通知 P2P1 放了奇数 → 通知 P2odd (init=0)P2 只取奇数
同步:偶数到达通知 P3P1 放了偶数 → 通知 P3even (init=0)P3 只取偶数

注意一个细节:P2 和 P3 之间没有直接同步关系——它们各取各的,被分开的奇 / 偶信号量隔离了。

信号量与代码

c
semaphore mutex = 1;     // 互斥访问缓冲区
semaphore empty = N;     // 缓冲区空闲单元数
semaphore odd   = 0;     // 缓冲区中已放入但未取走的奇数个数
semaphore even  = 0;     // 缓冲区中已放入但未取走的偶数个数

P1() {
    while (true) {
        x = produce();         // 生成一个正整数
        P(empty);              // 等空单元
        P(mutex);              // 占用缓冲区
        put();                 // 放入
        V(mutex);              // 让出缓冲区
        if (x % 2 == 0)
            V(even);           // 唤醒等待偶数的 P3
        else
            V(odd);            // 唤醒等待奇数的 P2
    }
}

P2() {
    while (true) {
        P(odd);                // 等"有奇数可取"
        P(mutex);              // 占用缓冲区
        getodd();              // 取出奇数
        V(mutex);              // 让出缓冲区
        V(empty);              // 缓冲区多出 1 个空单元
        countodd();            // 统计(不需要互斥,本地变量)
    }
}

P3() {
    while (true) {
        P(even);               // 等"有偶数可取"
        P(mutex);              // 占用缓冲区
        geteven();             // 取出偶数
        V(mutex);              // 让出缓冲区
        V(empty);              // 缓冲区多出 1 个空单元
        counteven();           // 统计
    }
}

信号量含义说明(答题必写)

信号量初值含义
mutex1缓冲区互斥锁,保证 put/getodd/geteven 三种操作不并发
emptyN缓冲区空闲单元数;P1 进入前要先 P(empty),取走后 V(empty)
odd0缓冲区中待取的奇数个数;P1 放奇数后 V,P2 取走前 P
even0缓冲区中待取的偶数个数;P1 放偶数后 V,P3 取走前 P

易错点逐条复盘

编者注(易错点 / 关键设计)

  1. P 操作的顺序P(empty) 必须在 P(mutex) 之前——否则会死锁。设想 P1 已拿到 mutex 但 empty=0,它会卡在 P(empty) 上而 mutex 不释放,P2/P3 也进不来 → 死锁。永远是"等资源的同步信号量在外层、互斥信号量在内层"

  2. 唤醒 P2 / P3 必须在 V(mutex) 之后吗? 不必。本题答案把 V(odd)/V(even) 放在 V(mutex) 之后,逻辑上没问题(先释放锁、再发通知)。但也可以放在 V(mutex) 前——因为 V 操作不会阻塞、不会引起死锁,只影响 P2/P3 唤醒的时机。两种写法都给分。

  3. 不要给 P2 和 P3 共用一个"data 信号量":错误写法是定义 semaphore data = 0,P1 放完后 V(data)、P2 P3 都 P(data)。这样会导致奇偶错配——P3 可能 P 到一个,但里面是奇数,无法处理(geteven 取不到)。正确做法必须奇偶各一个信号量

  4. 统计函数(countodd / counteven)放在 V(mutex) 之外:因为统计是 P2 或 P3 的本地变量操作,与缓冲区无关,没必要在临界区内执行——能减小临界区粒度、提高并发度。这是 408 评分细节里的"加分项"。

  5. 奇 + 偶信号量值之和总是等于"缓冲区当前已放但未取的总数":可以作为答题时的不变式自检。如果代码里出现"V(odd) 后又 V(odd)"或类似失衡情况,多半是逻辑写错了。

最后更新:

🎬 可视化演示
加载中...

提示:可在可视化区直接操作播放、步进、修改参数