精简版 · 小杯2026-08 冻结,已停止更新(发布前修订了 4 处已知错误)。后续勘误与新增内容只在正式版。看正式版(中杯)→
Skip to content

信号量

考情分析

信号量和 PV 操作是进程同步章节的核心工具,几乎所有同步互斥的大题都基于信号量。这是 92 分同步章节中最核心的知识点。🔥🔥🔥 绝对核心。

锁只有"开/关"两种状态,只能解决互斥。但很多场景需要更细粒度的控制——比如缓冲区有 N 个空位,允许 N 个生产者同时放入,但第 N+1 个必须等。信号量就像停车场入口的计数牌——有空位才放行,没空位就排队等。

信号量的定义

信号量(Semaphore)是一个整型变量,只能通过两个原子操作来访问:

  • P 操作(wait / proberen / 申请)
  • V 操作(signal / verhogen / 释放)

记录型信号量

c
typedef struct {
    int value;           // 信号量的值
    struct process *L;   // 等待队列
} semaphore;

P 操作

P(S) {
    S.value--;
    if (S.value < 0) {
        将当前进程加入 S.L;
        block();         // 阻塞当前进程
    }
}

V 操作

V(S) {
    S.value++;
    if (S.value <= 0) {
        从 S.L 中唤醒一个进程;
        wakeup(P);
    }
}

易错

S.value 的物理含义(选择题/简答题高频):

  • S.value > 0:有 S.value 个资源可用
  • S.value = 0:没有资源可用,也没有进程等待
  • S.value < 0|S.value| 个进程正在等待该资源

常见错误:认为 S.value < 0 时绝对值表示"已分配的资源数"——错,表示的是等待进程数

用信号量实现互斥

semaphore mutex = 1;    // 初值为 1

// 进程 P1              // 进程 P2
P(mutex);               P(mutex);
临界区;                  临界区;
V(mutex);               V(mutex);

互斥信号量初值为 1(表示同一时刻最多 1 个进程进入临界区)。

用信号量实现同步

让 P1 的操作 A 在 P2 的操作 B 之前执行:

semaphore S = 0;        // 初值为 0

// 进程 P1              // 进程 P2
操作 A;                  P(S);     ← 等待 P1 完成 A
V(S);                   操作 B;    ← A 完成后才执行 B

同步信号量初值为 0——「前 V 后 P」。

用信号量实现前驱关系

如果有更复杂的执行顺序要求(如 S1→S2, S1→S3, S2→S4, S3→S4):

为每条前驱边设置一个同步信号量(初值均为 0):

semaphore a=0, b=0, c=0, d=0;

P1: S1; V(a); V(b);
P2: P(a); S2; V(c);
P3: P(b); S3; V(d);
P4: P(c); P(d); S4;

信号量的使用要点

要点说明
P/V 必须成对漏写 P → 互斥失效;漏写 V → 死锁
互斥信号量初值为 1表示一次只允许一个进程
同步信号量初值为 0表示事件还没发生
P/V 顺序不能错有多个 P 操作时顺序至关重要(下篇详述)

同步互斥大题的解题步骤

同步互斥大题看着千变万化,其实都能套同一套流程走。记住这五步,再陌生的题也有下手的地方:

  1. 找出各类进程和各类资源 —— 先理清有几"类"进程(生产者 / 消费者、读者 / 写者……)、几"类"资源(缓冲区、打印机……),别把同一类的当成不同的。
  2. 分析进程之间的同步、互斥关系 —— 谁和谁互斥(抢同一临界资源)、谁必须等谁先做(同步 / 前驱),顺带想清楚哪种交错会导致死锁。
  3. 定义信号量并确定初值 —— 一个关系配一个信号量:互斥信号量初值为 1,同步信号量初值为 0,资源信号量初值为资源个数。
  4. 写出伪代码 —— 把 P、V 放到各进程的正确位置:进临界区前 P、出临界区后 V;等事件的一方 P、做完事件的一方 V。
  5. 回头检查 —— P/V 是否成对?会不会死锁(尤其多个 P 的先后顺序)?是否完全符合题意?

最容易翻车的一步

第 3、4 步里,当一个进程要同时 P 多个信号量时,对互斥信号量 mutex 的 P 要放在最后(最贴近临界区)、先 P 资源信号量。顺序反了就可能"抱着锁去等资源"而死锁——下一篇的经典问题会反复用到这条。

本文对应考点

  • 🔥🔥🔥 用信号量实现互斥(mutex=1, P-临界区-V)
  • 🔥🔥🔥 用信号量实现同步(S=0, 前V后P)
  • 🔥🔥🔥 S.value 的物理含义(正=剩余资源数,负=等待进程数)
  • 🔥🔥 P/V 操作的原子性(通过关中断实现)
  • 🔥 用信号量实现前驱关系

信号量很强大但编程容易出错。下一篇看管程和条件变量如何提供更安全的同步抽象。

真题练习