P/V操作详解与相关考点(软考知识点)

先记含义:P申请一个许可,没有许可就等待;V增加一个许可,必要时唤醒一名等待者。 信号量既可数可用资源,也可数已经发生、尚未被使用的事件通知。

1. 为什么需要它

进程或线程交错访问共享数据,可能发生竞争。两线程同时读计数器10,再各加1写回,可能最终只有11。共享打印机、缓冲队列等是临界资源;访问这些资源、必须受保护的那段代码是临界区。P/V可帮助建立两类约束:

约束 核心问题 例子
互斥 谁能同时进入 一次只允许一个线程修改队列
同步 谁必须先完成 数据准备完成后才能处理

同一个系统常同时需要两者。PV不能替代业务状态校验、数据库事务或完整的分布式一致性协议。

2. 信号量和原语

信号量通常记S,可以理解为许可计数,并配有等待队列。初值由"起点有几个许可"决定。初始5台可用打印机设S=5;初始空闲的互斥许可设mutex=1;某一次事件尚未完成设done=0。

P也常称wait/down,V也常称signal/up;POSIX接口常见sem_wait、sem_post。重点是语义,而非词源。

P/V对同一信号量的关键计数、判断、入队/唤醒更新需原子地协调,不能用两段未经保护的普通加减替代。原子不意味着等待时占着CPU;记录型阻塞信号量在等待时让出CPU。持锁的业务代码仍可能被抢占,互斥约束的是其他遵守同一锁协议的访问者能否进入临界区。

Linux sem_wait、sem_post。

3. 考试数值计算:统一看题目模型

下文采用教材常见"记录型、P先减1"的模型:

复制代码
P(S):
    S = S - 1
    若 S < 0:
        当前进程加入S的等待队列并阻塞
​
V(S):
    S = S + 1
    若 S <= 0:
        唤醒S等待队列中的一个进程

关键共享状态操作由原语原子地协调;此伪代码描述语义,不是可直接用普通整数写出的并发程序。

S的值 该模型下的含义
S>0 还有S个未分配许可
S=0 没有剩余许可;不能单凭0判断是否有人正处于临界区
S<0 没有剩余许可,等待队列中有-S名等待者

P执行后等于0,不阻塞;小于0才阻塞。V执行后等于0,仍需唤醒一个等待者。 不要把P的"<0"和V的"<=0"记反。

实际系统接口可以使用非负许可值加独立等待队列,查询值也未必返回负等待人数。因此负数计算只按题设模型使用,不把教材S的表达直接等同某操作系统API。UIC同步课程、sem_getvalue。

4. 手工走一遍:两份资源、四个申请者

初值S=2,甲乙丙丁各申请一份。下列顺序已约定,先不讨论谁优先被唤醒。

步骤 S变化 结果
甲P 2→1 获得许可
乙P 1→0 获得许可,仍可继续
丙P 0→-1 阻塞
丁P -1→-2 阻塞
甲V -2→-1 唤醒一名等待者,假定丙
乙V -1→0 再唤醒丁
丙使用完成后V 0→1 归还一份许可
丁使用完成后V 1→2 全部许可归还

负数不是"资源数量变成负数",而是该模型把等待人数计入了S。丙被唤醒后,原来阻塞的P完成,不用额外再执行一次P、再减一次。

被唤醒的进程通常阻塞→就绪,获得CPU后才运行;V不保证被唤醒者立即抢占执行。阻塞等待不等于持续检查条件的自旋。

独立小例:初值S=3,7名进程各执行一次P且期间无人V,则S=3-7=-4,有4名等待者。随后有2名持有者各V一次,S=-2,唤醒2名,仍有2名等待者。这里资源总量仍是3。

5. 用P/V实现互斥

初始临界区空闲:

复制代码
mutex = 1
​
每个访问者:
    P(mutex)
    读取计数器
    加1
    写回计数器
    V(mutex)

P放在完整共享数据操作前,V放在完整操作后。只保护最后一次写回,不能消除前面读取旧值造成的竞争。

mutex初值1表示最初能放行一个访问者;初值2会允许两个同时进入,不能提供要求的互斥;初值0且没人能先V,会让首次申请者都等待。所有相关访问者应遵守同一个互斥协议。

常见错误:遗漏V使后续申请者可能一直等待;多执行一次V可能制造额外许可、破坏互斥。不同锁只保护各自协议下的访问,不能靠变量名一样就认为锁是共享的。

6. 用P/V实现先后同步:对应369题

要求A准备一次数据,B随后处理一次:

复制代码
done = 0
​
线程A:
    准备数据
    V(done)
​
线程B:
    P(done)
    处理数据

A负责让事件发生,完成后V;B需要等待事件,处理前P。等待者P,完成者V。

  • done初值1:B可能在A尚未准备时直接通过,破坏先后关系。

  • A在准备前先P(done):A会被阻塞;若唯一通知又依赖A完成,系统无法推进。

  • A在准备前就V(done):B可能过早读到未准备的数据。

  • A先完成V、B后执行P:许可被保存,B仍能通过,不会因为"V发生时没人等"就丢掉这次通知。

互斥常由同一访问者P(mutex)/V(mutex)包住临界区;先后同步可以由不同线程对同一信号量进行V和P,不能机械套"一个线程必须在同一信号量上先P再V"。

7. 前驱关系图:分支与汇合

例如要求A完成后B、C才能分别开始;B和C均完成后D才能开始。B与C之间没有先后约束。

复制代码
       B
     ↗   ↘
A           D
     ↘   ↗
       C

一种容易检查的办法是每条依赖边使用一个初值0的信号量:

复制代码
sAB = sAC = sBD = sCD = 0
​
A:执行A;V(sAB);V(sAC)
B:P(sAB);执行B;V(sBD)
C:P(sAC);执行C;V(sCD)
D:P(sBD);P(sCD);执行D

D要收齐两个不同前驱的完成信号。不要只等一次就执行D;也不要让B、C竞争同一个只有一份的许可,却误以为两者都已获通知。边信号量方案便于保证每条依赖,未必是所有题目的最少信号量方案。

8. 生产者消费者:互斥与同步同时存在

缓冲区容量N、初始已有m个产品:

复制代码
empty = N - m
full  = m
mutex = 1

容量不是mutex的初值;mutex保护修改队列这一段代码,一次仍只允许一个访问者。empty/full表示能否进行业务,mutex表示谁能修改共享结构。

信号量 P的含义 V的含义
empty 申请一个空槽 通知腾出一个空槽
full 申请一个产品 通知增加一个产品
mutex 进入修改队列的临界区 退出临界区
复制代码
生产者反复:
    在临界区外准备产品
    P(empty)
    P(mutex)
    放入产品并更新队列
    V(mutex)
    V(full)
​
消费者反复:
    P(full)
    P(mutex)
    取出产品并更新队列
    V(mutex)
    V(empty)
    在临界区外处理产品

生产者申请空槽、产生产品;消费者申请产品、释放空槽。所以生产者P(empty)对应消费者V(empty),消费者P(full)对应生产者V(full),它们跨角色协作。

先申请空槽或产品,再申请mutex。 在此典型无其他隐藏锁的模型中,操作结束后的两条V可存在其他安全先后变体;不要把所有V顺序互换一律判为死锁。OSTEP信号量章节。

9. 为什么案例会死锁

案例容量3,起初为空,所以初值empty3/full0/mutex1。题目随后给的是缓冲区已满的时刻,不能继续把初始empty3套过去。

满时没有可用空槽。错误生产者先P(mutex)拿锁,再P(empty)等待空槽;消费者要先获得mutex才能取出产品、腾出空槽。

复制代码
生产者持mutex → 等空槽
空槽要消费者取出产品才产生
消费者要取出产品 → 等mutex

没有其他释放路径,两者相互等待。阻塞的生产者不会自动归还它之前拿到的mutex;P(empty)失败也不是自动回滚之前所有P。

修正申请顺序后,满时生产者会先在P(empty)等待,尚未拿mutex,消费者就能进入取走产品。空时消费者先P(full)等待、不持mutex,生产者也能进入放产品。

10. 相关题型与边界

题型 检查方法
求初值 问起点能立即发出几个许可:资源N,空闲互斥1,未完成事件0
判断阻塞/唤醒 按题设记录型模型逐步加减,P后<0阻塞,V后<=0唤醒
求等待人数 记录型S<0时为-S,不能当负资源数
求最多并发数 N份同类许可、每人占一份,可有N名占用者;临界区mutex1只放行1名
填P/V位置 先识别互斥区,再识别前驱事件;不要一见P就认为是加互斥锁
判断死锁 列每个进程持有什么、等什么,以及谁能解除等待
判断状态 P导致等待则运行→阻塞;被V唤醒通常阻塞→就绪
检查配对 互斥用同一许可申请归还;事件和产品许可可以跨线程传递
检查原子性 PV内部原子,不代表后续整段业务自动原子;业务仍需正确包住临界区
判断公平性 题目未说明队列策略,不能默认FIFO或断言一定不会饥饿

多锁场景若甲先L1后L2、乙先L2后L1,可能循环等待。统一全局申请顺序破坏循环等待;不等于强制夺走资源。

读者写者题再增加"多个读者可并发读,写者需独占"的规则;读者计数自身也可能需要互斥,读/写优先策略可能造成另一方饥饿。哲学家就餐题则常表现为拿着一个资源再等另一个,仍回到资源申请顺序与循环等待。先用本讲义的基本方法列清持有与等待,再处理组合算法。

empty/full在没有预留与等待者计入的稳定状态可以对应实际空槽/产品数量;若题目追踪P成功但放入/取出尚未完成的中间步骤,或采用负等待计数,不能无条件套empty+full=N。

11. 你的复习重点

369:done0,A完成后V,B处理前P。 案例B4:初值3/0/1只是第一步,还要说清持锁等空位形成的循环,并写全生产者/消费者顺序。 下一轮应能不看讲义写出:P和V含义;记录型负数与唤醒;互斥1和事件0的区别;两套有界缓冲区顺序;错误顺序下各方等什么。再用新题和延迟复测验证,不因读过讲义标为已掌握。

相关推荐
嘟哩DuliDuli6 小时前
AI创作工作区的项目上下文与多模态任务边界
android·人工智能·安全·ai·软件工程
用AI来创业吧1 天前
网站建设公司哪家好:报价差 3 倍,差在哪四项成本上(选型笔记)
笔记·性能优化·软件工程·安全威胁分析
seconp2 天前
基于Spring Boot和Vue 3的智慧旅游网站:住宿预订、行程规划与千问接入
vue.js·spring boot·毕业设计·软件工程·课程设计·旅游·毕设
地图小课堂2 天前
Bigemap Pro 教程|地方独立坐标系 CAD 图纸,怎么导入套合卫星影像
软件工程
海海不掉头发2 天前
软件开发方法第七周笔记_CSDN博客
人工智能·笔记·学习·软件工程
ueotek2 天前
Ansys Zemax丨在 OpticStudio 中拟合折射率数据
python·软件工程·ansys·光学仿真·光学设计·zeamx
旋生万物2 天前
螺旋数不是玄学:Python 实现广义复数与等角螺线
开发语言·人工智能·python·云原生·aigc·软件工程·后端开发
旋生万物3 天前
全息结构入门:Embedding、向量库、知识图谱的统一视角
人工智能·大模型·aigc·软件工程·知识图谱·embedding·后端开发