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

详解前驱图与PV操作

前驱图、PV操作

  • 前驱图与PV操作的结合
    • 例子:两个进程的同步问题
    • 使用PV操作实现同步
  • 前驱图的实际应用
  • 更复杂的场景
  • 示例
    • 示例1:前驱图与PV操作的结合
      • 1. 前驱图表示
      • 2. 使用信号量(PV操作)实现同步
        • 进程的执行逻辑:
      • 3. 示例代码
      • 4. 解释
      • 5. 输出结果示例:
    • 示例2:信号量分配
      • 1. 示例背景
      • 2. 信号量的分配
        • 3. 信号量初始值:
      • 4. 信号量的分配与操作
      • 5. 信号量分配的详细步骤
        • 6. 初始状态:
        • 7. 信号量状态变化的解释:
      • 8. 示例代码
      • 9. 总结

前驱图(precedence graph)是一种用于表示并发系统中进程或事件之间依赖关系的有向图。在并发编程和同步领域,前驱图用于说明哪些操作必须先发生,哪些可以并行,哪些需要等待其他操作完成。它有助于理解多个进程如何协调执行,避免冲突或死锁等问题。

PV操作是一种经典的进程同步机制,它源自Dijkstra的信号量(Semaphore)理论。PV操作用于管理进程对共享资源的访问,防止并发进程中的竞争条件。P操作(Proberen,尝试)是对信号量减1的操作,如果信号量大于0,进程继续执行;否则,进程阻塞。V操作(Verhogen,增加)是对信号量加1的操作,释放资源,允许其他进程继续执行。

前驱图与PV操作的结合

在并发系统中,前驱图可以用来直观地描述进程的执行顺序和依赖关系,而PV操作用于实现这些依赖关系所需的同步。二者结合使用,能够通过信号量控制进程的执行,使其严格遵循前驱图的依赖顺序。

例子:两个进程的同步问题

假设有两个进程 AB,它们分别进行以下步骤:

  • 进程 A 依次执行操作 A1A2
  • 进程 B 依次执行操作 B1B2

但是,它们之间有依赖关系:

  • A2 必须在 B1 之后执行,也就是说,A2 的前驱是 B1

用前驱图来表示这个依赖关系,图中会有一条从 B1 指向 A2 的有向边,表示 A2 依赖于 B1 的完成。

使用PV操作实现同步

我们可以使用信号量 S 来控制 A2B1 之间的执行顺序。初始时,信号量 S=0,表示 A2 不能立即执行。

  1. 进程 B 在执行完 B1 后,执行 V(S) 操作,表示 B1 完成并释放信号量。
  2. 进程 A 在执行 A2 前,执行 P(S) 操作。由于 S 的初始值为0,A2 会阻塞,直到 B1 完成并且 S 增加到1,A2 才能执行。

前驱图的实际应用

前驱图和PV操作结合的关键在于:

  • 前驱图:确定并发系统中事件的依赖顺序。
  • PV操作:通过信号量实现这种顺序的同步控制。

通过这种方式,可以确保进程按照预期的顺序执行,避免资源竞争和死锁。例如,在数据库系统中,多个事务对相同数据的并发访问可以通过PV操作控制,保证数据一致性。在操作系统中,多个进程对共享内存的访问也可以通过前驱图建模,并结合PV操作确保正确的同步执行。

更复杂的场景

对于复杂的并发场景,前驱图可能会包含多个有向边,表示多个前驱事件。相应的PV操作可能涉及多个信号量。例如,如果 A3 依赖于 B2C1 的完成,则在 A3 执行前需要等待两个信号量释放。

这种前驱图与PV操作结合的机制不仅限于简单的进程同步,还可以扩展到多进程通信、死锁预防、生产者-消费者问题等各种并发控制问题。

示例

示例1:前驱图与PV操作的结合

假设有三个进程 ABC,并且它们执行的任务如下:

  • 进程 A:任务 A1 -> 任务 A2
  • 进程 B:任务 B1 -> 任务 B2
  • 进程 C:任务 C1 -> 任务 C2

任务之间有如下依赖关系:

  1. A2 必须在 B1 完成后才能执行。
  2. B2 必须在 C1 完成后才能执行。

1. 前驱图表示

我们可以用前驱图表示这些依赖关系:

  • B1 → A2:表示 A2 依赖 B1,即 A2 只有在 B1 完成后才能执行。
  • C1 → B2:表示 B2 依赖 C1,即 B2 只有在 C1 完成后才能执行。

这个前驱图可以画成:

B1 → A2
C1 → B2

2. 使用信号量(PV操作)实现同步

为了实现上述依赖关系,我们引入两个信号量:

  • 信号量 S1,用于控制 A2B1 的依赖。初始值为 0。
  • 信号量 S2,用于控制 B2C1 的依赖。初始值为 0。

初始情况下,两个信号量 S1S2 都为 0,表示 A2B2 都不能立即执行,必须等待相应的前驱任务完成。

进程的执行逻辑:
  • 进程 A

    1. 执行 A1
    2. 执行 P(S1) 操作(等待信号量 S1),当 S1 > 0 时,才执行 A2
  • 进程 B

    1. 执行 B1
    2. 执行 V(S1) 操作,释放信号量 S1,允许 A2 执行。
    3. 执行 P(S2) 操作(等待信号量 S2),当 S2 > 0 时,才执行 B2
  • 进程 C

    1. 执行 C1
    2. 执行 V(S2) 操作,释放信号量 S2,允许 B2 执行。

3. 示例代码

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>// 定义信号量
sem_t S1, S2;void* processA(void* arg) {printf("Process A: Executing A1\n");// 等待信号量S1,确保A2在B1之后执行sem_wait(&S1);printf("Process A: Executing A2 after B1\n");return NULL;
}void* processB(void* arg) {printf("Process B: Executing B1\n");// B1完成后,释放信号量S1,允许A2执行sem_post(&S1);// 等待信号量S2,确保B2在C1之后执行sem_wait(&S2);printf("Process B: Executing B2 after C1\n");return NULL;
}void* processC(void* arg) {printf("Process C: Executing C1\n");// C1完成后,释放信号量S2,允许B2执行sem_post(&S2);return NULL;
}int main() {// 初始化信号量sem_init(&S1, 0, 0); // S1 初始为0,A2不能立即执行sem_init(&S2, 0, 0); // S2 初始为0,B2不能立即执行pthread_t threadA, threadB, threadC;// 创建线程,模拟三个进程的执行pthread_create(&threadA, NULL, processA, NULL);pthread_create(&threadB, NULL, processB, NULL);pthread_create(&threadC, NULL, processC, NULL);// 等待线程完成pthread_join(threadA, NULL);pthread_join(threadB, NULL);pthread_join(threadC, NULL);// 销毁信号量sem_destroy(&S1);sem_destroy(&S2);return 0;
}

4. 解释

  • 信号量的初始化sem_init(&S1, 0, 0)sem_init(&S2, 0, 0) 将信号量 S1S2 初始化为 0。表示 A2B2 不能立即执行,必须等待它们的依赖任务完成。

  • 进程 AA1 立即执行,执行到 A2 时,它会阻塞在 sem_wait(&S1) 处,直到 B1 完成并释放 S1

  • 进程 BB1 执行后调用 sem_post(&S1),释放信号量 S1,允许 A2 执行。B2 需要等待 C1 完成,调用 sem_wait(&S2) 进行同步。

  • 进程 CC1 执行后调用 sem_post(&S2),释放信号量 S2,允许 B2 执行。

5. 输出结果示例:

Process B: Executing B1
Process A: Executing A1
Process C: Executing C1
Process A: Executing A2 after B1
Process B: Executing B2 after C1

示例2:信号量分配

1. 示例背景

假设我们有三个进程 ABC,它们执行的任务如下:

  • 进程 AA1 -> A2
  • 进程 BB1 -> B2
  • 进程 CC1 -> C2

任务之间有以下依赖关系:

  1. A2 必须在 B1 完成后执行。
  2. B2 必须在 C1 完成后执行。

2. 信号量的分配

我们将使用信号量 S1S2 来同步这些依赖关系:

  1. 信号量 S1 用于控制 A2B1 之间的依赖。
  2. 信号量 S2 用于控制 B2C1 之间的依赖。
3. 信号量初始值:
  • S1 = 0:表示 A2B1 完成之前不能执行。
  • S2 = 0:表示 B2C1 完成之前不能执行。

4. 信号量的分配与操作

  1. 初始状态:所有信号量的初始值为 0,表示依赖任务尚未完成,相关任务需要等待前驱任务完成后才能执行。

  2. 进程 A

    • A1 任务可以立即执行,不依赖任何其他任务。
    • A2 任务需要等待 B1 任务完成。我们使用 sem_wait(S1) 来阻塞 A2 的执行,直到 B1 释放信号量 S1
  3. 进程 B

    • B1 任务可以立即执行,不依赖其他任务。
    • 执行完 B1 后,进程 B 调用 sem_post(S1),将 S1 的值从 0 增加到 1,释放信号量,使 A2 能够继续执行。
    • B2 任务依赖于 C1 任务的完成,因此在 B2 任务执行前调用 sem_wait(S2),阻塞 B2,直到 C1 完成并释放信号量 S2
  4. 进程 C

    • C1 任务可以立即执行,不依赖任何其他任务。
    • 执行完 C1 后,进程 C 调用 sem_post(S2),将 S2 的值从 0 增加到 1,允许 B2 执行。

5. 信号量分配的详细步骤

为了使这个过程更加清晰,让我们以具体的执行步骤和信号量状态变化为例:

6. 初始状态:
  • S1 = 0A2 需要等待 B1 完成)
  • S2 = 0B2 需要等待 C1 完成)
操作说明S1 信号量状态S2 信号量状态
A1 执行无需等待,直接执行00
B1 执行无需等待,直接执行00
sem_post(S1)B1 完成,释放 S1,允许 A2 执行10
A2 执行A2 依赖 B1S1 = 1,可以执行00
C1 执行无需等待,直接执行00
sem_post(S2)C1 完成,释放 S2,允许 B2 执行01
B2 执行B2 依赖 C1S2 = 1,可以执行00
7. 信号量状态变化的解释:
  1. 初始状态S1 = 0S2 = 0,这意味着 A2B2 不能立即执行。
  2. B1 完成后B1 执行完毕后,进程 B 通过 sem_post(S1) 将信号量 S1 增加到 1,表示 A2 可以执行。
  3. A2 执行A2 检查 S1 是否大于 0,发现信号量已被释放,于是继续执行。
  4. C1 完成后C1 执行完毕后,进程 C 通过 sem_post(S2) 将信号量 S2 增加到 1,表示 B2 可以执行。
  5. B2 执行B2 检查 S2 是否大于 0,发现信号量已被释放,于是继续执行。

8. 示例代码

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>// 定义信号量
sem_t S1, S2;void* processA(void* arg) {printf("Process A: Executing A1\n");// 等待信号量S1,确保A2在B1之后执行sem_wait(&S1);printf("Process A: Executing A2 after B1\n");return NULL;
}void* processB(void* arg) {printf("Process B: Executing B1\n");// B1完成后,释放信号量S1,允许A2执行sem_post(&S1);// 等待信号量S2,确保B2在C1之后执行sem_wait(&S2);printf("Process B: Executing B2 after C1\n");return NULL;
}void* processC(void* arg) {printf("Process C: Executing C1\n");// C1完成后,释放信号量S2,允许B2执行sem_post(&S2);return NULL;
}int main() {// 初始化信号量sem_init(&S1, 0, 0); // S1 初始为0,A2不能立即执行sem_init(&S2, 0, 0); // S2 初始为0,B2不能立即执行pthread_t threadA, threadB, threadC;// 创建线程,模拟三个进程的执行pthread_create(&threadA, NULL, processA, NULL);pthread_create(&threadB, NULL, processB, NULL);pthread_create(&threadC, NULL, processC, NULL);// 等待线程完成pthread_join(threadA, NULL);pthread_join(threadB, NULL);pthread_join(threadC, NULL);// 销毁信号量sem_destroy(&S1);sem_destroy(&S2);return 0;
}

9. 总结

  • **信

号量分配的关键在于将每个信号量与特定的任务依赖关系相关联,以确保进程按照正确的顺序执行

在这个示例中:

  • S1 控制 A2 任务在 B1 任务完成后执行。
  • S2 控制 B2 任务在 C1 任务完成后执行。

通过初始化信号量为 0,我们确保依赖任务(如 A2B2)不会提前执行,只有在前驱任务完成并释放信号量时,依赖任务才能继续运行。

这种信号量的分配和使用可以广泛应用于多进程或多线程程序中,帮助有效地管理复杂的同步问题,避免竞争条件和不确定的执行顺序。

相关文章:

详解前驱图与PV操作

前驱图、PV操作 前驱图与PV操作的结合例子&#xff1a;两个进程的同步问题使用PV操作实现同步 前驱图的实际应用更复杂的场景示例示例1&#xff1a;前驱图与PV操作的结合1. 前驱图表示2. 使用信号量&#xff08;PV操作&#xff09;实现同步进程的执行逻辑&#xff1a; 3. 示例代…...

孩子来加拿大上学真的那么轻松吗?(上)

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 卷圈 运营 / SandLiu 卷圈 监制 / 姝琦 封面 / 姝琦Midjourney 产品统筹 / bobo 这是拼娃时代第三十一期节目&#xff0c;经过了一年的沉寂&#xff0c;拼娃时代在今年九月份终于恢复更新啦&#xff0c;JunJun老师也…...

【算法篇】二叉树类(1)(笔记)

目录 一、认识二叉树 1. 二叉树的种类 &#xff08;1&#xff09;满二叉树 &#xff08;2&#xff09;完全二叉树 &#xff08;3&#xff09;二叉搜索树 &#xff08;4&#xff09;平衡二叉搜索树 2. 二叉树的存储方式 3. 二叉树的遍历方式 4. 二叉树的定义 二、Leet…...

《C++无锁编程:解锁高性能并发的新境界》

在当今的软件开发领域&#xff0c;并发编程的重要性日益凸显。随着多核处理器的普及&#xff0c;开发者们越来越需要利用并发来提高程序的性能和响应速度。而 C作为一种强大的编程语言&#xff0c;提供了多种技术来实现无锁编程&#xff0c;从而在并发环境下获得更高的性能和更…...

系统架构设计师教程 第9章 9.5 软件可靠性测试 笔记

9.5 软件可靠性测试 ★★★☆☆ 9.5.1 软件可靠性测试概述 软件测试者可以使用很多方法进行软件测试&#xff0c;如按行为或结构来划分输入域的划分测试&#xff0c; 纯粹随机选择输入的随机测试&#xff0c;基于功能、路径、数据流或控制流的覆盖测试等。 软件可靠性测试由可…...

如何使用ssm实现校园体育赛事管理系统的设计与实现+vue

TOC ssm713校园体育赛事管理系统的设计与实现vue 绪论 课题背景 身处网络时代&#xff0c;随着网络系统体系发展的不断成熟和完善&#xff0c;人们的生活也随之发生了很大的变化。目前&#xff0c;人们在追求较高物质生活的同时&#xff0c;也在想着如何使自身的精神内涵得…...

CSS 中的文本相关属性(line - height、font、letter - 属性、text - 属性)

目录 非 VIP 用户可前往公众号回复“css”进行免费阅读 line - height属性 字号与行高的取值约定 行高与盒子高度的关系 font、letter -属性 、text -属性 font属性 letter -属性 text - 属性 非 VIP 用户可前往公众号回复“css”进行免费阅读 line - height属性 字号与…...

mobaxterm、vscode通过跳板机连接服务器

目标服务器&#xff1a;111.111.11.11 跳板机&#xff1a;100.100.10.10 1. mobaxterm通过跳板机连接服务器 1.1 目标服务器信息 1.2 跳板机信息 1.3 登录 点击登录&#xff0c;会输入密码&#xff0c;成功 参考&#xff1a;https://blog.csdn.net/qq_40636486/article/det…...

鸿萌数据恢复:iPhone 手机被盗后应采取哪些措施?警惕这些骗局

天津鸿萌科贸发展有限公司从事数据安全服务二十余年&#xff0c;致力于为各领域客户提供专业的数据恢复、数据备份解决方案与服务&#xff0c;并针对企业面临的数据安全风险&#xff0c;提供专业的相关数据安全培训。 丢失昂贵的 iPhone 不仅会造成较大的经济损失&#xff0c;还…...

为了学习Python熬夜部署了Jupyter Notebook 6.x

文章目录 Docker拉取并构建容器安装部署jupyter对Jupyter使用过程问题总结1 没有代码提示怎么办&#xff1f;2 如果想切换python版本了怎么办&#xff1f;3 想在jupyter里面使用vim怎么办&#xff1f; 遇见的问题参考文章 怎么说&#xff0c;今天在学习Python的时候&#xff0c…...

docker-文件复制(docker cp:用于在Docker主机和容器之间拷贝文件或目录)

文章目录 1、把宿主机的文件复制到容器内部1.1、查询 宿主机 root 下的文件1.2、docker cp /root/anaconda-ks.cfg spzx-redis:/root1.3、查看 spzx-redis 容器 中/root目录下是否有 anaconda-ks.cfg 文件 2、把容器中的文件 复制 到宿主机中2.1、查看 spzx-redis 容器 / 下的文…...

guava里常用功能

guava 是 Google 提供的一个 Java 库&#xff0c;提供了很多实用的工具类和方法&#xff0c;可以帮助开发者更高效地编写代码。以下是一些常用的 Guava 工具类及其功能示例&#xff1a; 1. Lists 用于操作列表的工具类。 import com.google.common.collect.Lists;List<In…...

su 命令:一键切换用户身份、提高su命令安全性的建议

一、命令简介 ​su ​命令是 Linux 和 Unix 系统中的一个实用工具&#xff0c;用于切换用户身份。它允许当前登录用户在不退出登录会话的情况下&#xff0c;切换到另一个用户的身份。通常&#xff0c;su ​用于从普通用户切换到 root 用户&#xff0c;或从 root 用户切换到其他…...

观察者模式(发布-订阅模式)

用途&#xff1a; &#xff08;1&#xff09;可用于拦截过滤器 &#xff08;2&#xff09;订单创建成功后的一些后续逻辑&#xff08;消息提醒&#xff0c;订单打印&#xff0c;物品打包等&#xff09; &#xff08;3&#xff09;需要由统一调度中心调度的一系列任务等 消息…...

耦合微带线单元的网络参量和等效电路公式推导

文档下载链接&#xff1a;耦合微带线单元的网络参量和等效电路资源-CSDN文库https://download.csdn.net/download/lu2289504634/89583027笔者水平有限&#xff0c;错误之处欢迎留言&#xff01; 一、耦合微带线奇偶模详细推导过程 二、2,4端口开路 三、2端口短路、3端口开路 四…...

elasticsearch的Ingest Attachment插件的使用总结

安装 Ingest Attachment 插件 确保 Elasticsearch 已安装&#xff1a; 首先&#xff0c;请确保你已经安装并运行了 Elasticsearch。可以通过访问 http://localhost:9200 来检查是否正常运行。 安装插件&#xff1a; 使用以下命令在 Elasticsearch 中安装 Ingest Attachment 插…...

SemiDrive E3 MCAL 开发系列(4) – Gpt 模块的使用

一、 概述 本文将会介绍SemiDrive E3 MCAL GPT模块的基本配置&#xff0c;并且会结合实际操作的介绍&#xff0c;帮助新手快速了解并掌握这个模块的使用&#xff0c;文中的 MCAL 是基于 PTG3.0 的版本&#xff0c;开发板是官方的 E3640 网关板。 二、 Gpt 模块的主要配置 …...

前端导出页面PDF

import html2canvas from html2canvas import { jsPDF } from jspdf import { Loading } from element-ui let downloadLoadingInstance// 导出页面为PDF格式---使用插件html2canvas和jspdf插件 export function exportPDF(fileName, node) {downloadLoadingInstance Loading.…...

Jenkins的安装

1.简介 官网&#xff1a;https://www.jenkins.io 中文文档&#xff1a;Jenkins Jenkins 是一个开源的持续集成&#xff08;CI&#xff09;工具&#xff0c;用于自动化构建、测试和部署软件项目。它提供了一个易于使用和可扩展的平台&#xff0c;帮助团队更高效地开发和交付软…...

初学51单片机之I2C总线与E2PROM

首先先推荐B站的I2C相关的视频I2C入门第一节-I2C的基本工作原理_哔哩哔哩_bilibili 看完视频估计就大概知道怎么操作I2C了&#xff0c;他的LCD1602讲的也很不错&#xff0c;把数据建立tsp和数据保持thd&#xff0c;比喻成拍照时候的摆pose和按快门两个过程&#xff0c;感觉还是…...

C语言数组探秘:数据操控的艺术【下】

承接上篇&#xff0c;我们继续讲数组的内容。 八.二维数组的使用 当我们掌握了二维数组的创建和初始化&#xff0c;那我们怎么使用二维数组呢&#xff1f;其实二维数组访问也是使用下标的形式的&#xff0c;二维数组是有行和列的&#xff0c;只要锁定了行和列就能唯一锁定数组中…...

Jmeter关联,断言,参数化

目录 一、关联 边界提取器 JSON提取器 正则表达式提取器 跨线程关联 二、断言 响应断言 JSON断言 断言持续时间 三、参数化 用户参数 csv data setconfig csvread函数 一、关联 常用的关联有三种 1.边界提取器 2.JSON提取器 3.正则表达式提取器 接下来就详细讲述…...

嵌入式单片机底层原理详解

前言 此笔记面向有C语言基础、学习过数字电路、对单片机有一定了解且尚在学习阶段的群体编写,笔记中会介绍单片机的结构、工作原理,以及一些C语言编程技巧,对于还停留在复制模板、copy代码阶段的读者会有比较大的帮助,待学习完成后可以独立完成几乎所有单片机的驱动开发。 …...

重修设计模式-行为型-责任链模式

重修设计模式-行为型-责任链模式 将请求的发送和接收解耦&#xff0c;让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链&#xff0c;并沿着这条链传递这个请求&#xff0c;直到链上的某个接收对象能够处理它为止。 责任链模式&#xff08;Chain of Responsibilit…...

Vercel部署/前端部署

Vercel 部署 今天要讲的是如何对别人向自己的开源仓库提的PR进行自动代码审核 1. 注册并登录Vercel 访问 Vercel官网点击右上角的"Sign Up"选择使用GitHub、GitLab、Bitbucket或邮箱注册完成注册流程并登录 2. 连接代码仓库 在Vercel仪表板,点击"New Proje…...

常见的css预处理器

CSS预处理器是一种扩展了CSS功能的脚本语言&#xff0c;它允许开发者以编程的方式编写更加干净、结构化的CSS代码。通过引入变量、嵌套规则、混合&#xff08;Mixins&#xff09;、函数等高级特性&#xff0c;CSS预处理器使得CSS代码的编写更加灵活、高效&#xff0c;同时也提高…...

mysql—半同步模式

mysql的并行复制 在172.25.254.20(slave)主机上 默认情况下slave中使用的是sql单线程回放 在master中时多用户读写&#xff0c;如果使用sql单线程回放那么会造成组从延迟严重 开启MySQL的多线程回放可以解决上述问题 mysql> show processlist; 在配置文件中进行编辑 [root…...

You are not allowed to push code to this project

原因1 用户权限不够。 具体查看用户权限路径&#xff1a; 原因2 vscode之前都能提交代码&#xff0c;但是突然就提交不上了。 表现为:前端代码能拉取&#xff0c;但是不能提交。使用idea进行前端代码的提交&#xff0c;完全没问题。 解决方案&#xff1a;修改TortoiseG…...

Java刷题:最小k个数

目录 题目描述&#xff1a; 思路&#xff1a; 具体实现 整体建立一个大小为N的小根堆 通过大根堆实现 完整代码 力扣链接&#xff1a;面试题 17.14. 最小K个数 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 设计一个算法&#xff0c;找出数组中最小的…...

Redis实战--Redis应用过程中出现的热门问题及其解决方案

Redis作为一种高性能的key-value数据库&#xff0c;广泛应用于缓存、消息队列、排行榜等场景。然而&#xff0c;在实际应用中&#xff0c;随着业务规模的不断扩大和访问量的持续增长&#xff0c;缓存系统也面临着诸多挑战&#xff0c;其中最为典型的便是缓存穿透、缓存击穿和缓…...