当前位置: 首页 > news >正文

探索PV操作:并发编程的核心钥匙

PV操作是解决并发进程(或线程)中互斥同步问题的核心工具,由荷兰计算机科学家艾兹格·迪科斯彻(Edsger W. Dijkstra) 在1960年代提出。这个名字来源于荷兰语:

  • P()操作Proberen):意为“尝试”或“探查”。对应到信号量的操作就是申请资源获取锁
  • V()操作Verhogen):意为“增加”或“释放”。对应到信号量的操作就是释放资源解锁

在中文语境里,也常被称为 “原语” (Primitive),意指不可中断的操作。


1. 核心概念:信号量(Semaphore)

要理解PV操作,必须先理解其操作的对象——信号量(Semaphore)

你可以把信号量想象成一个特殊的整型变量,但其值不能直接加减,只能通过PV操作来改变。它伴随着一个等待队列,用于存放等待该信号量的进程。

信号量有两种主要类型:

  • 整型信号量:早期的简单实现。
  • 记录型信号量:现代操作系统更常用的实现,解决了整型信号量“忙等”的问题。

我们通常用 S 来表示一个信号量。


2. PV操作的定义

P(S) 操作
  1. 将信号量 S 的值减1
  2. 如果减1后,S 的值大于等于0,说明资源充足,该进程继续执行。
  3. 如果减1后,S 的值小于0,说明资源已被占用完毕。则该进程被阻塞,并放入与信号量 S 相关的等待队列中,等待其他进程执行 V(S) 操作来唤醒它。

伪代码实现(记录型信号量):

void P(semaphore S) { // P操作原语S.value--;        // 申请一份资源if (S.value < 0) { // 如果资源已经分配完add this process to S.waiting_queue; // 当前进程进入等待队列block(); // 自我阻塞,放弃CPU控制权}
}
V(S) 操作
  1. 将信号量 S 的值加1
  2. 如果加1后,S 的值大于0,说明没有进程在等待该资源,操作结束。
  3. 如果加1后,S 的值小于等于0(注意:这里通常是<=0),说明等待队列中有至少一个进程正在阻塞等待该资源。则从等待队列中唤醒一个进程,让其就绪,然后V操作结束。

伪代码实现(记录型信号量):

void V(semaphore S) { // V操作原语S.value++;        // 释放一份资源if (S.value <= 0) { // 如果有进程在等待remove a process P from S.waiting_queue; // 从等待队列中移出一个进程Pwakeup(P); // 唤醒进程P,将其放入就绪队列}
}

关键点:

  • PV操作是原子性的:即在执行P或V操作时,不会被中断,保证了在检查信号量和修改信号量之间不会有其他进程插入。
  • 负值的意义:信号量的值如果为负数,其绝对值代表正在等待该资源的进程数量

3. PV操作的两种应用场景

场景一:实现进程互斥(Mutual Exclusion)

保证多个进程在访问临界资源(一次仅允许一个进程使用的资源,如打印机、共享变量等)时不会产生冲突。

方法:

  1. 初始化一个互斥信号量 mutex,并将其值设为 1(表示只有一个单位的资源,即临界区每次只允许一个进程进入)。
  2. 将临界区代码放在 P(mutex)V(mutex) 之间。

例子:

semaphore mutex = 1; // 初始化互斥信号量Process_A() {while (true) {P(mutex);       // 申请进入临界区(上锁)// ... 临界区代码 ... // 访问共享资源V(mutex);       // 离开临界区(解锁)// ... 剩余代码 ...}
}Process_B() {while (true) {P(mutex);       // 申请进入临界区(上锁)// ... 临界区代码 ... // 访问共享资源V(mutex);       // 离开临界区(解锁)// ... 剩余代码 ...}
}

过程分析:

  • 进程A先执行 P(mutex)mutex 从1变为0。由于0>=0,A进入临界区。
  • 此时若进程B也执行 P(mutex)mutex 从0变为-1。由于-1<0,进程B被阻塞,放入等待队列。
  • 进程A执行完临界区后,执行 V(mutex)mutex 从-1变为0。由于0<=0,它唤醒等待队列中的进程B。
  • 进程B被唤醒,从就绪状态变为运行状态,得以进入临界区。
  • 进程B执行 V(mutex) 后,mutex 从0变回1。
场景二:实现进程同步(Synchronization)

协调多个进程的执行顺序,让某些进程在某些点停下来等待,直到另一个进程完成特定操作发出“信号”。

方法:
初始化一个同步信号量 S,并根据需要设置其初始值(通常为 0)。

经典例子:生产者-消费者问题

  • 规则:生产者生产产品,消费者消费产品。消费者不能消费空缓冲区中的产品,生产者也不能向满缓冲区中投放产品。
  • 需要两个同步信号量和一个互斥信号量:
    • empty:表示空闲缓冲区的数量,初始值为 N(缓冲区总大小)。
    • full:表示已装满产品的缓冲区数量,初始值为 0
    • mutex:用于对缓冲区链路的互斥访问,初始值为 1

代码框架:

semaphore empty = N; // 空闲缓冲区信号量
semaphore full = 0;  // 产品数量信号量
semaphore mutex = 1; // 互斥信号量Producer() { // 生产者进程while (true) {produce an item;         // 生产一个产品P(empty);               // 申请一个空缓冲区(空缓冲区数减1)P(mutex);               // 申请进入临界区(锁住缓冲区)add the item to buffer; // 将产品放入缓冲区V(mutex);               // 离开临界区(解锁缓冲区)V(full);                // 释放一个满缓冲区(产品数加1)}
}Consumer() { // 消费者进程while (true) {P(full);                // 申请一个产品(产品数减1)P(mutex);               // 申请进入临界区(锁住缓冲区)remove an item from buffer; // 从缓冲区取出一个产品V(mutex);               // 离开临界区(解锁缓冲区)V(empty);               // 释放一个空缓冲区(空缓冲区数加1)consume the item;       // 消费该产品}
}

同步过程分析:

  • V(full)P(full) 实现了同步:一开始 full=0,消费者想消费时必须先执行 P(full),由于 full=0 减1后为-1,消费者会被阻塞。直到生产者生产了一个产品并执行了 V(full)full从-1变为0)后,才会唤醒消费者。
  • V(empty)P(empty) 同样实现了同步:如果缓冲区满了(empty=0),生产者想生产时必须先执行 P(empty),会被阻塞,直到消费者消费后执行 V(empty) 来唤醒它。

4. 总结与注意事项

  • 核心地位:PV操作是理解操作系统并发控制的基石,后续的很多高级同步机制(如管程)都是在它的基础上发展而来的。
  • 成对出现:P操作和V操作必须成对出现。缺少P会导致无法互斥,缺少V会导致资源永不释放,进程永久阻塞(死锁)。
  • 顺序重要P操作的顺序至关重要。错误的顺序极易导致死锁。例如在生产者-消费者模型中,如果生产者将两个P操作颠倒:P(mutex); P(empty);。假设缓冲区已满,生产者先拿到 mutex 锁,然后在 P(empty) 时被阻塞。此时消费者想消费,但执行 P(mutex) 时发现锁已被生产者拿走且阻塞了,所以消费者也会被阻塞。双方互相等待,形成死锁。
  • 信号量初值:根据应用场景(互斥 or 同步)正确设置信号量的初始值。
http://www.dtcms.com/a/391038.html

相关文章:

  • 一计算机网络基本概念-体系结构-思考题
  • Teslasuit动捕服的实际应用,系统利用电肌肉刺激为用户在VR中提供逼真的感觉和触觉
  • 【DMA】深入解析DMA控制器架构与运作原理
  • wayland 下 带特殊权限的 Qt GUI 程序 部署为 开机自启+守护进程
  • 无事随笔——mp踩坑
  • 根据后端给定的swagger文档生成对应的ts接口
  • 《黑天鹅》
  • docker编写java的jar步骤
  • HDR简介
  • 视觉Slam14讲笔记第4讲李群李代数【更新中】
  • 【无人机】ardupilot事项笔记
  • 大端模式与小端模式
  • Openwrt 平台下移植rk3568 rknn_yolov5_demo 应用程序问题分析
  • Dioxus后端代码
  • 概念篇:ReactJS + AppSync + DynamoDB 性能优化核心概念
  • 实践篇:ReactJS + AppSync + DynamoDB 性能优化实践
  • GPS 定位:守护财产安全的 “隐形防盗锁”
  • Vue3 + Three.js 进阶实战:批量 3D 模型高效可视化、性能优化与兼容性解决方案
  • 海外VPS索引版本兼容性检查,版本兼容问题检测与多系统适配方法
  • uniapp 常用
  • C语言入门教程 | 阶段一:基础语法讲解(数据类型与运算符)
  • 现代AI工具深度解析:从GPT到多模态的技术革命与实战应用
  • 自由学习记录(101)
  • 2025最新口红机防篡改版本源码
  • Unity2D-图片导入设置
  • 今日赛事前瞻:德甲:斯图加特VS圣保利,意甲:莱切VS卡利亚里
  • AWS CloudTrail 监控特定 SQS 队列事件完整配置指南
  • 【算法】【优选算法】BFS 解决 FloodFill 算法
  • 量化交易 - Stochastic Gradient Descent Regression (SGDRegressor) 随机梯度下降回归 - 机器学习
  • AWS WAF防护IoT设备劫持攻击:智能设备安全防护实践