跳转至

进程同步与互斥:信号量、PV 操作和生产者—消费者

这部分属于 操作系统 → 进程管理 → 进程同步与互斥。前驱图描述任务之间的先后关系,信号量和 PV 操作则用于实现等待、通知和互斥访问;生产者—消费者是把这些机制组合起来的经典模型。

学习时先确定“哪些进程要协作、共享什么、各自在等什么”,再写信号量和 PV 操作。

1. 同步和互斥分别解决什么问题

概念 要解决的问题 流水线中的例子
同步 让任务在条件满足后继续,协调先后关系 箱子有空位才能放,有产品才能取
互斥 同一时刻只允许一个执行流进入相应临界区,避免冲突访问共享数据 放入或取出时,需要保护箱子的存取位置等共享数据

临界区 是访问共享资源、需要互斥保护的那段程序。箱子是共享资源,修改它的存取位置、放入或取出数据的代码属于临界区。初步加工、继续加工、检验等工作通常可以在临界区外完成。

同步关注“条件够不够”,互斥关注“现在谁能操作”。即使箱子还有空位,生产者也可能因为消费者正在操作箱子而等待锁。

2. 信号量是什么,P 和 V 又是什么

信号量是一种同步机制,可以先理解为 带等待功能的许可计数器。它既可以表达可用资源数量,也可以表示一个任务已经完成、另一个任务可以继续。

S3 = 0

这里 S3 是变量名,0 是初值。“3”不是信号量当前的数值,也不表示它必然属于进程 P3。

记号 含义
P1、P2、P3 进程的名字
P(S),也称 wait 申请一个许可;没有可用许可时阻塞等待
V(S),也称 signal 归还或增加一个许可,使一个等待者有机会继续

P 不是生产,V 不是消费。 加工、放入、取出、检验是实际工作;PV 操作控制这些工作何时可以执行。生产者和消费者都会使用 P,也都会使用 V。

P 也不只是“检查一下”:成功通过 P 就取得了一个许可。例如 P(empty) 会预留一个空位,避免多个生产者把同一个空位重复使用。P/V 必须保证原子性以及正确的等待、唤醒协调,普通变量的加减不能直接替代它们。

P(S3) 是否意味着 S3 等于 1

不意味着。假设 S3 表示可领取半成品的许可,在没有并发操作和等待者的简单情况下:

动作 可用许可数
箱子最初为空 0
放入一件,执行 V(S3) 1
再放入一件,执行 V(S3) 2
消费者成功执行一次 P(S3) 1

一次 P 申请一个许可,S3 则管理可能有多个的许可。 许可已经被领取、物品还没实际取出时,两者之间存在短暂的预订状态,所以运行中的许可数不一定等于箱子里肉眼可见的物品数。

补充:有些教材为什么会出现负数信号量

上文用“可用许可数”帮助理解,数量不会小于 0。教材中的记录型信号量还可以把等待者编码到同一个整数中,常见规则是:

  • P:先将值减 1,若结果小于 0,就阻塞调用者。
  • V:先将值加 1,若结果小于或等于 0,就唤醒一个等待者。
  • 值为负时,其绝对值表示等待者数量;不是有“负数件产品”。

例如初值为 0,一个进程先执行 P,值变为 -1 并阻塞;另一个进程执行 V,值变为 0,前者被唤醒并完成这次 P,不需要再额外减一次。做数值变化题时,要先确认题目采用哪一种定义,不能混用这两种计数表达。

3. 互斥信号量、同步信号量与初值

“互斥”和“同步”描述信号量的用途,并不是通过 S 后面的编号区分的。

用途 它管理的许可 常见初值
互斥访问 进入临界区的一份访问权 初始未加锁时设为 1
前驱完成通知 前驱已经完成、后继可以继续的许可 前驱尚未完成时设为 0
空位计数 可申请的缓冲区位置 初始空位数
物品计数 可领取的缓冲区物品 初始物品数

不存在适合所有场景的自动“默认值”。应先问:这个信号量代表什么?开始时有几份许可?

容量为 N、初始已有 k 件物品的缓冲区,通常设置为:

1
2
3
mutex = 1       一次只允许一个进程操作共享数据
empty = N − k   最初有多少空位
full  = k       最初有多少件物品可取

例如容量 5、已有 2 件物品:mutex = 1、empty = 3、full = 2。互斥信号量初值为 1 与箱子容量无关;同步信号量也不一定从 0 开始。

两类信号量都需要 P、V 配合

互斥信号量通常在 同一个进程里 包围临界区:

P(mutex) → 操作共享数据 → V(mutex)

同步信号量通常由 不同进程 协作:

生产者放入物品 → V(full)     消费者 P(full) → 取物品
消费者取出物品 → V(empty)    生产者 P(empty) → 放物品

不能说“同步信号量不成对”,也不能要求任意时刻 P、V 的执行次数都一样。初值本身可以提供许可,例如初始空位为 N,生产者不必等到第一次 V(empty) 才能使用空位。

这里的同进程、跨进程是常见用法。信号量通常没有专用互斥锁那样的持有者约束,不能把所有二值信号量直接等同于互斥锁。

4. 前驱图:用 PV 实现“先完成,再开始”

前驱图是描述先后依赖的有向无环图。A → B 表示 A 的相应任务完成后,B 才能开始;有多个前驱时,必须等所有前驱条件满足。

题目中的关系是:

flowchart LR
    P1 --> P2
    P2 --> P3
    P2 --> P4
    P3 --> P5
    P4 --> P5

P3、P4 没有相互的先后约束,可以并发执行;P5 必须等两者都完成,并不要求 P3、P4 同时开始或结束。

按本题“每条依赖边设置一个信号量”的方法,五个信号量初值都设为 0:

依赖边 信号量 前驱完成后 后继开始前
P1 → P2 S1 P1 执行 V(S1) P2 执行 P(S1)
P2 → P3 S2 P2 执行 V(S2) P3 执行 P(S2)
P2 → P4 S3 P2 执行 V(S3) P4 执行 P(S3)
P3 → P5 S4 P3 执行 V(S4) P5 执行 P(S4)
P4 → P5 S5 P4 执行 V(S5) P5 执行 P(S5)

对应原题 a~g 的填写结果:

位置 内容 原因
a V(S1) P1 完成后通知 P2
b P(S1) P2 等待 P1
c V(S2)、V(S3) P2 分别通知 P3、P4
d P(S2) P3 等待 P2
e V(S4) P3 完成后通知 P5
f P(S3) P4 等待 P2
g P(S4)、P(S5) P5 等 P3、P4 都完成

图中 P4 结束处的 V(S5) 已经给出。按这些选项,第 15 题选 A,第 16 题选 B;第 17 空对应 f、g 的上述组合。

解题时,看一个进程的 全部直接前驱和后继:在本题方案下,几条边进入,就在开始前放几个对应的 P;几条边出去,就在完成后放几个对应的 V。

信号量的名字可以整体换,只要两端配对一致。b 写 P(S1) 是因为 a 发的是 V(S1),并不是因为 P2 应该匹配某个固定编号。g 的两个 P 可以交换顺序,但都必须通过。单次 V 只提供一个许可,不会自动通知所有后继。

5. 两级生产者—消费者流水线

题目中,P1 初步加工,P2 继续加工,P3 检验:

P1 初步加工 → 半成品箱 B1 → P2 继续加工 → 成品箱 B2 → P3 检验

P1 是 B1 的生产者;P2 既是 B1 的消费者,又是 B2 的生产者;P3 是 B2 的消费者。两个箱子分别需要空位许可、物品许可和互斥锁。

六个信号量分别代表什么

B1 容量为 n,B2 容量为 m,两箱最初为空:

箱子 信号量 含义 用途 初值
B1 S1 B1 的操作许可 互斥 1
B1 S2 B1 的空位许可 同步、数量条件 n
B1 S3 B1 的半成品许可 同步、数量条件 0
B2 S5 B2 的操作许可 互斥 1
B2 S4 B2 的空位许可 同步、数量条件 m
B2 S6 B2 的成品许可 同步、数量条件 0

因此第 21 题选 C:S1、S5 都是互斥信号量,初值均为 1。第 22 空是 n、m。

这里的 S1~S6 与上一道前驱图题中的同名变量没有关系,每道题都要重新识别含义。

P1:预留空位,再拿锁,最后放入

1
2
3
4
5
6
7
初步加工        做出一个半成品,属于实际生产
P(S2)           申请一个 B1 空位;没有空位就等待
P(S1)           取得 B1 的操作锁;锁被占用就等待
半成品 → B1     实际放入箱子
V(S1)           释放 B1 的锁
V(S3)           提供一个可取半成品的许可
重复上述过程

P(S2) 负责“有没有空位,并预留一个”;P(S1) 负责“现在能不能由我操作箱子”。末尾 V(S3) 表示有一件半成品可取,让 P2 有机会继续,不是把物品消费掉。

P2:先消费 B1,再生产到 B2

P(S3)           等待并领取一份 B1 半成品许可
P(S1)           取得 B1 的锁
从 B1 取半成品
V(S1)           释放 B1 的锁
V(S2)           归还一个 B1 空位许可

继续加工        在临界区外完成

P(S4)           申请一个 B2 空位
P(S5)           取得 B2 的锁
产品 → B2
V(S5)           释放 B2 的锁
V(S6)           提供一个可取成品的许可
重复上述过程

P3:取走成品后归还空位

1
2
3
4
5
6
7
P(S6)           等待并领取一份 B2 成品许可
P(S5)           取得 B2 的锁
从 B2 取产品
V(S5)           释放 B2 的锁
V(S4)           归还一个 B2 空位许可
检验产品        在临界区外完成
重复上述过程

为什么先等空位或物品,再拿锁

如果消费者先拿锁,再在空箱子上等物品,就可能出现:

  1. 消费者持有箱子的锁,执行 P(full) 后等待物品。
  2. 生产者想放入物品,却在 P(mutex) 处等消费者释放锁。
  3. 消费者等物品,生产者等锁,双方都不能继续。

因此这个模型的基本顺序是:先等数量条件,再拿锁,操作箱子,释放锁,更新另一类许可。 锁只保护必要的共享操作,避免持锁等待对方才能提供的条件。

6. 做题时按什么顺序分析

  1. 找实际工作和共享资源。 区分加工、放入、取出等动作,以及需要保护的共享数据。
  2. 给每个信号量写一句含义。 用“B1 空位”“B1 物品”“B1 锁”替代机械记忆 S1、S2、S3。
  3. 按初始状态数许可。 锁未占用通常是 1;空位是容量减已有物品数;物品是已有数量;尚未发生的完成通知是 0。
  4. 看 P 申请了什么、V 提供了什么。 同一个信号量的申请和释放应能对应起来,也要考虑初始已有的许可。
  5. 检查等待与加锁顺序。 尤其检查是否持有别人必需的锁,却在等待别人才能满足的条件。
  6. 检查多前驱、多后继。 是否等齐所有必要条件,是否提供了足够的完成许可。

前驱图与资源分配图不要混淆:前驱图的边连接任务,表达先后依赖;资源分配图连接进程与资源,表达申请或分配,用于分析资源等待与死锁。参见死锁与资源分配图。

7. 思维导图

--- markmap: height: 640 initialExpandLevel: 2 autoFit: true colorFreezeLevel: 2 maxWidth: 280 spacingHorizontal: 80 spacingVertical: 12 nodeMinHeight: 24 paddingX: 10 --- # 进程同步与互斥 ## 为什么需要协调 - 同步:条件满足后继续 - 互斥:保护共享数据的临界区 ## 信号量与 PV - S 的编号只是名字,值由初值和操作决定 - P:申请一个许可,没有就等待 - V:归还或增加一个许可 - P 不是生产,V 不是消费 - P/V 需要原子性和正确的等待唤醒 ## 初值怎样确定 - 互斥且锁可用:1 - 尚未发生的完成通知:0 - 空位:容量减已有物品数 - 物品:初始已有物品数 ## P/V 的配合 - 互斥:通常同一进程加锁、解锁 - 同步:通常不同进程提供、领取许可 - 初始许可允许先 P,不必先 V ## 前驱图 - 边表示完成后才能开始的依赖 - 每边一信号量时:前驱 V,后继 P - 多前驱要等齐,多后继要分别通知 ## 生产者与消费者 - 每箱一组 empty、full、mutex - 先申请空位或物品,再拿锁 - 操作箱子后解锁,再提供另一类许可 - 加工和检验通常放在临界区外

返回操作系统章节