PV操作的C++代码示例讲解

article/2025/6/9 19:38:38

文章目录

    • 一、PV操作基本概念
      • (一)信号量
      • (二)P操作
      • (三)V操作
    • 二、PV操作的意义
    • 三、C++中实现PV操作的方法
      • (一)使用信号量实现PV操作
        • 代码解释:
      • (二)使用互斥量和条件变量实现PV操作
        • 代码解释:
    • 四、PV操作的经典问题及解决方案
      • (一)生产者 - 消费者问题
        • 解决方案:
        • 代码解释:
      • (二)读者 - 写者问题
        • 解决方案:
        • 代码解释:
    • 五、总结

一、PV操作基本概念

PV操作是操作系统中用于进程同步的一种经典机制,由荷兰计算机科学家Dijkstra提出,用于解决多进程/线程的互斥与同步问题。它由P操作和V操作两个原子操作组成,通过对信号量进行操作来控制多个进程之间对共享资源的访问。

(一)信号量

信号量是一个特殊的整型变量,用于表示可用资源的数量。其值仅能由P、V操作改变,可分为公用信号量和私用信号量:

  • 公用信号量:用于实现进程间的互斥,初值通常设为1,相关的进程均可在此信号量上执行P操作和V操作。
  • 私用信号量:用于实现进程间的同步,初始值通常设为0或正整数,仅允许拥有此信号量的进程执行P操作,而其他相关进程可在其上施行V操作。

(二)P操作

P操作(Proberen,尝试)表示一个进程试图获得一个资源。具体步骤如下:

  1. 将信号量S的值减1,即S = S - 1。
  2. 如果S >= 0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。

(三)V操作

V操作(Verhogen,增加)表示一个进程释放一个资源。具体步骤如下:

  1. 将信号量S的值加1,即S = S + 1。
  2. 如果S > 0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。

二、PV操作的意义

PV操作的主要意义在于实现进程的同步和互斥,属于进程的低级通信方式。

  • 同步:进程间速度有差异,快的进程需要等待慢的进程,通过PV操作可以协调进程的执行顺序,保证进程间的正确协作。
  • 互斥:同一时刻只能由一个进程访问临界资源,通过PV操作可以对临界区进行加锁和解锁,确保同一时间只有一个进程能够进入临界区。

三、C++中实现PV操作的方法

C++中没有直接的PV操作,但是可以通过标准库中的互斥量、条件变量、信号量等机制来实现类似的功能。下面将通过几个具体的例子来详细讲解。

(一)使用信号量实现PV操作

在C++中,可以使用<semaphore.h>头文件中的sem_waitsem_post函数来实现P操作和V操作。以下是一个简单的示例,模拟多个进程对共享资源的访问:

#include <iostream>
#include <semaphore.h>
#include <pthread.h>// 定义信号量
sem_t sem;// P操作函数
void P() {sem_wait(&sem);
}// V操作函数
void V() {sem_post(&sem);
}// 线程函数
void* threadFunction(void* arg) {int id = *(int*)arg;std::cout << "线程 " << id << " 尝试访问资源..." << std::endl;P();std::cout << "线程 " << id << " 正在访问资源..." << std::endl;// 模拟访问资源的时间sleep(1);std::cout << "线程 " << id << " 释放资源..." << std::endl;V();return nullptr;
}int main() {// 初始化信号量,初始值为1,表示只有一个资源可用sem_init(&sem, 0, 1);const int numThreads = 3;pthread_t threads[numThreads];int threadIds[numThreads];// 创建线程for (int i = 0; i < numThreads; ++i) {threadIds[i] = i;pthread_create(&threads[i], nullptr, threadFunction, &threadIds[i]);}// 等待所有线程结束for (int i = 0; i < numThreads; ++i) {pthread_join(threads[i], nullptr);}// 销毁信号量sem_destroy(&sem);return 0;
}
代码解释:
  1. 信号量初始化:在main函数中,使用sem_init函数初始化信号量sem,初始值为1,表示只有一个资源可用。
  2. P操作和V操作:定义了PV函数,分别调用sem_waitsem_post函数来实现P操作和V操作。
  3. 线程函数threadFunction函数模拟了线程对共享资源的访问过程,先调用P函数申请资源,访问资源后再调用V函数释放资源。
  4. 线程创建和等待:在main函数中,创建了多个线程,并使用pthread_join函数等待所有线程结束。
  5. 信号量销毁:使用sem_destroy函数销毁信号量。

(二)使用互斥量和条件变量实现PV操作

除了信号量,还可以使用<mutex><condition_variable>头文件中的互斥量和条件变量来实现PV操作。以下是一个生产者 - 消费者问题的示例:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>std::mutex mtx;
std::condition_variable cv;
std::queue<int> buffer;
const int bufferSize = 5;// 生产者线程函数
void producer() {for (int i = 0; i < 10; ++i) {std::unique_lock<std::mutex> lock(mtx);// 等待缓冲区有空闲位置cv.wait(lock, [] { return buffer.size() < bufferSize; });buffer.push(i);std::cout << "生产者生产了 " << i << std::endl;// 通知消费者有新数据cv.notify_one();lock.unlock();}
}// 消费者线程函数
void consumer() {for (int i = 0; i < 10; ++i) {std::unique_lock<std::mutex> lock(mtx);// 等待缓冲区有数据cv.wait(lock, [] { return !buffer.empty(); });int item = buffer.front();buffer.pop();std::cout << "消费者消费了 " << item << std::endl;// 通知生产者有空闲位置cv.notify_one();lock.unlock();}
}int main() {std::thread t1(producer);std::thread t2(consumer);t1.join();t2.join();return 0;
}
代码解释:
  1. 互斥量和条件变量:使用std::mutexstd::condition_variable来实现线程同步。
  2. 生产者线程:在producer函数中,使用cv.wait函数等待缓冲区有空闲位置,生产数据后使用cv.notify_one函数通知消费者有新数据。
  3. 消费者线程:在consumer函数中,使用cv.wait函数等待缓冲区有数据,消费数据后使用cv.notify_one函数通知生产者有空闲位置。
  4. 线程创建和等待:在main函数中,创建了生产者线程和消费者线程,并使用join函数等待它们结束。

四、PV操作的经典问题及解决方案

(一)生产者 - 消费者问题

生产者 - 消费者问题是一个经典的并发编程问题,涉及到多个线程共享有限缓冲区的情况。生产者线程负责向缓冲区中生产数据,而消费者线程负责从缓冲区中消费数据。需要确保在并发执行的情况下,生产者和消费者之间的操作是正确有序的,避免数据竞争和死锁等问题。

解决方案:

使用三个信号量来解决该问题:

  • empty:表示缓冲区的空槽数,初始值为缓冲区的大小。
  • full:表示缓冲区的数据数,初始值为0。
  • mutex:用于实现对缓冲区的互斥访问,初始值为1。

以下是使用信号量实现的生产者 - 消费者问题的示例代码:

#include <iostream>
#include <semaphore.h>
#include <pthread.h>#define N 5sem_t empty;
sem_t full;
sem_t mutex;
int buffer[N];
int in = 0;
int out = 0;// 生产者线程函数
void* producer(void* arg) {for (int i = 0; i < 10; ++i) {sem_wait(&empty);sem_wait(&mutex);buffer[in] = i;std::cout << "生产者生产了 " << i << std::endl;in = (in + 1) % N;sem_post(&mutex);sem_post(&full);}return nullptr;
}// 消费者线程函数
void* consumer(void* arg) {for (int i = 0; i < 10; ++i) {sem_wait(&full);sem_wait(&mutex);int item = buffer[out];std::cout << "消费者消费了 " << item << std::endl;out = (out + 1) % N;sem_post(&mutex);sem_post(&empty);}return nullptr;
}int main() {sem_init(&empty, 0, N);sem_init(&full, 0, 0);sem_init(&mutex, 0, 1);pthread_t producerThread, consumerThread;pthread_create(&producerThread, nullptr, producer, nullptr);pthread_create(&consumerThread, nullptr, consumer, nullptr);pthread_join(producerThread, nullptr);pthread_join(consumerThread, nullptr);sem_destroy(&empty);sem_destroy(&full);sem_destroy(&mutex);return 0;
}
代码解释:
  1. 信号量初始化:在main函数中,初始化三个信号量emptyfullmutex
  2. 生产者线程:在producer函数中,先调用sem_wait(&empty)申请空槽,再调用sem_wait(&mutex)申请对缓冲区的访问权,生产数据后调用sem_post(&mutex)sem_post(&full)释放资源。
  3. 消费者线程:在consumer函数中,先调用sem_wait(&full)申请数据,再调用sem_wait(&mutex)申请对缓冲区的访问权,消费数据后调用sem_post(&mutex)sem_post(&empty)释放资源。
  4. 线程创建和等待:在main函数中,创建生产者线程和消费者线程,并使用pthread_join函数等待它们结束。
  5. 信号量销毁:使用sem_destroy函数销毁信号量。

(二)读者 - 写者问题

读者 - 写者问题是另一个经典的并发编程问题,允许多个读者同时对文件进行读操作,但只允许一个写者往文件写信息,任一写者在完成写操作之前不允许其他读者或写者工作。

解决方案:

使用两个信号量和一个计数器来解决该问题:

  • rw_mutex:用于实现对文件的读写互斥,初始值为1。
  • count_mutex:用于保护读者计数器reader_count,初始值为1。
  • reader_count:记录当前读者的数量。

以下是使用信号量实现的读者 - 写者问题的示例代码:

#include <iostream>
#include <semaphore.h>
#include <pthread.h>sem_t rw_mutex;
sem_t count_mutex;
int reader_count = 0;// 读者线程函数
void* reader(void* arg) {while (true) {sem_wait(&count_mutex);if (reader_count == 0) {sem_wait(&rw_mutex);}reader_count++;sem_post(&count_mutex);// 读取文件std::cout << "读者正在读取文件..." << std::endl;sem_wait(&count_mutex);reader_count--;if (reader_count == 0) {sem_post(&rw_mutex);}sem_post(&count_mutex);}return nullptr;
}// 写者线程函数
void* writer(void* arg) {while (true) {sem_wait(&rw_mutex);// 写入文件std::cout << "写者正在写入文件..." << std::endl;sem_post(&rw_mutex);}return nullptr;
}int main() {sem_init(&rw_mutex, 0, 1);sem_init(&count_mutex, 0, 1);pthread_t readerThread, writerThread;pthread_create(&readerThread, nullptr, reader, nullptr);pthread_create(&writerThread, nullptr, writer, nullptr);pthread_join(readerThread, nullptr);pthread_join(writerThread, nullptr);sem_destroy(&rw_mutex);sem_destroy(&count_mutex);return 0;
}
代码解释:
  1. 信号量初始化:在main函数中,初始化两个信号量rw_mutexcount_mutex
  2. 读者线程:在reader函数中,先调用sem_wait(&count_mutex)申请对计数器的访问权,若为第一个读者,则调用sem_wait(&rw_mutex)申请对文件的访问权,读取文件后再释放资源。
  3. 写者线程:在writer函数中,直接调用sem_wait(&rw_mutex)申请对文件的访问权,写入文件后再调用sem_post(&rw_mutex)释放资源。
  4. 线程创建和等待:在main函数中,创建读者线程和写者线程,并使用pthread_join函数等待它们结束。
  5. 信号量销毁:使用sem_destroy函数销毁信号量。

五、总结

通过以上示例可以看出,PV操作是一种强大的并发编程工具,可以有效地解决进程同步和互斥问题。在C++中,可以使用信号量、互斥量和条件变量等机制来实现PV操作。在实际应用中,需要根据具体的问题选择合适的解决方案,并注意P、V操作的顺序和次数,避免出现死锁等问题。同时,由于PV操作的并发性,程序的调试比较困难,需要仔细分析和排查问题。"


http://www.hkcw.cn/article/phDJPXfDHl.shtml

相关文章

医疗内窥镜影像工作站技术方案(续)——EFISH-SCB-RK3588国产化替代技术深化解析

一、异构计算架构的医疗场景适配 ‌多核任务调度优化‌ ‌A76/A55协同计算‌&#xff1a;4Cortex-A762.4GHz负责AI推理&#xff08;如息肉识别算法&#xff09;&#xff0c;4Cortex-A551.8GHz处理DICOM影像传输协议&#xff0c;多任务负载效率比赛扬N系列提升80%‌NPU加速矩阵…

HCIP-Datacom Core Technology V1.0_3 OSPF基础

动态路由协议简介 静态路由相比较动态路由有什么优点呢。 静态路由协议&#xff0c;当网络发生故障或者网络拓扑发生变更&#xff0c;它需要管理员手工配置去干预静态路由配置&#xff0c;但是动态路由协议&#xff0c;它能够及时自己感应网络拓扑变化&#xff0c;不路由选择…

敏捷转型:破局之道

在数字化浪潮与市场不确定性加剧的背景下&#xff0c;传统组织向敏捷组织转型已成为企业生存与发展的核心命题。这种转型并非简单的工具迭代或流程优化&#xff0c;而是涉及治理结构、文化基因与人才机制的深度重构。理解两种组织形态的本质差异&#xff0c;明确转型的适用场景…

WordPress 6.5版本带来的新功能

WordPress 6.5正式上线了&#xff01;WordPress团队再一次为我们带来了许多新的改进。在全球开发者的共同努力下&#xff0c;WordPress推出了许多新的功能&#xff0c;本文将对其进行详细总结。 Hostease的虚拟主机现已支持一键安装最新版本的WordPress。对于想要体验WordPres…

软硬解锁通用Switch大气层1.9.0系统+20.0.1固件升级 图文教程 附大气层大气层固件升级整合包下载

软硬解锁通用Switch大气层1.9.0系统20.0.1固件升级 图文教程 附大气层大气层固件升级整合包下载 大气层&#xff08;Atmosphere&#xff09;是为任天堂 Switch 主机开发的免费开源自定义固件&#xff08;CFW&#xff09;&#xff0c;由开发者 SciresM 领导的团队维护。它允许用…

Redisson学习专栏(五):源码阅读及Redisson的Netty通信层设计

文章目录 前言一、分布式锁核心实现&#xff1a;RedissonLock源码深度解析1.1 加锁机制&#xff1a;原子性与重入性实现1.2 看门狗机制&#xff1a;锁自动续期设计1.3 解锁机制&#xff1a;安全释放与通知1.4 锁竞争处理&#xff1a;等待队列与公平性1.5 容错机制&#xff1a;异…

字节新出的MCP应用DeepSearch,有点意思。

大家好&#xff0c;我是苍何。 悄悄告诉你个事&#xff0c;昨天我去杭州参加字节火山方舟举办的开发者见面会了&#xff0c;你别说&#xff0c;还真有点刘姥姥进大观园的感觉&#x1f436; 现场真实体验完这次新发布的产品和模型&#xff0c;激动的忍不住想给大家做一波分享。…

光耦电路学习,光耦输入并联电阻、并联电容,光耦输出滤波电路

一般的光耦电路&#xff0c;只需要输入限流电阻&#xff0c;输出上拉电阻即可。 实际使用时&#xff0c;比如工控等一些干扰大、存在浪涌电压等的场合&#xff0c;根据实际可以添加一些抗干扰电路、滤波电路&#xff0c;增加电路抗干扰能力。 比如&#xff1a; 1、给光耦输入两…

JVM知识

目录 运行时数据区域 程序计数器 Java虚拟机栈 局部变量表 操作数栈 动态链接 本地方法栈 Java堆 方法区 运行时常量池 字符串常量池 直接内存 Java对象的创建过程 对象的内存布局 对象的访问 常见的 GC 类型 ​​Minor GC&#xff08;Young GC&#xff09;​ …

Spring AI介绍及大模型对接

目录 1. Spring AI介绍 2. Spring AI常用组件 2.1. Chat Client API 2.2. Models 2.3. Vector Databases 2.4. RAG 2.5. MCP 3. 大模型对接举例 3.1. 获取deepseek的API keys 3.2. idea创建工程 3.3. 配置application.yml 3.4. 编写Controller测试类 3.5. 验证Con…

C++算法训练营 Day6 哈希表(1)

1.有效的字母异位词 LeetCode&#xff1a;242.有效的字母异位词 给定两个字符串s和t &#xff0c;编写一个函数来判断t是否是s的字母异位词。 示例 1: 输入: s “anagram”, t “nagaram” 输出: true 示例 2: 输入: s “rat”, t “car” 输出: false 解题思路&#xff…

LeetCode hot100-11

题目描述 题目链接&#xff1a;滑动窗口最大值 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1&#xff1a; 输入…

js web api阶段

一.变量声明 1.JS中的const const在js修饰数组和对象&#xff0c;本质类似与c的引用数据类型&#xff0c;所以类似于 int* const ref 修饰的是地址&#xff0c;值是可以改变的 然后下面这种情况是禁止的 左边这种都有括号&#xff0c;说明是建立了一个块新地址去存放&#xf…

【计算机网络】数据链路层——ARP协议

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;计算机网络 &#x1f339;往期回顾&#x1f339;&#xff1a;【计算机网络】网络层IP协议与子网划分详解&#xff1a;从主机通信到网络设计的底层逻辑 &#x1f516;流…

群晖 NAS 如何帮助培训学校解决文件管理难题

在现代教育环境中&#xff0c;数据管理和协同办公的效率直接影响到教学质量和工作流畅性。某培训学校通过引入群晖 NAS&#xff0c;显著提升了部门的协同办公效率。借助群晖的在线协作、自动备份和快照功能&#xff0c;该校不仅解决了数据散乱和丢失的问题&#xff0c;还大幅节…

基于LLaMA-Factory和Easy Dataset的Qwen3微调实战:从数据准备到LoRA微调推理评估的全流程指南

随着开源大模型如 LLaMA、Qwen 和 Baichuan 的广泛应用&#xff0c;其基于通用数据的训练方式在特定下游任务和垂直领域中的表现仍存在提升空间&#xff0c;因此衍生出针对具体场景的微调训练需求。这些训练涵盖预训练&#xff08;PT&#xff09;、指令微调&#xff08;SFT&…

视觉语言动作模型 (VLAs) :赋予机器行动的智慧

文章目录 一、VLA 的诞生&#xff1a;从单模态到多模态的飞跃二、深入剖析 VLA&#xff1a;核心组件与工作原理三、前沿进展&#xff1a;那些令人瞩目的 VLA 模型与趋势四、VLA 的广阔天地&#xff1a;应用场景一览五、挑战与荆棘&#xff1a;VLA 面临的难题六、未来展望&#…

C/S医学影像系统源码,全院一体化PACS系统源码,实现全院检查预约和信息共享互通

全院一体化PACS系统源码 全院级PACS系统不仅仅具有安全、高效、稳定的访问/存储/调阅架构和强大的影像后台处理功能&#xff1b;还是一个全院一体化的PACS系统&#xff0c;覆盖了医院所有影像科室&#xff08;放射、超声、内镜、病理、心脑电等&#xff09;&#xff1b;从影像…

力扣刷题Day 69:搜索二维矩阵(74)

1.题目描述 2.思路 首先判断target是否有可能在矩阵的某一行里&#xff0c;没可能直接返回False&#xff0c;有可能就在这一行里二分查找。 3.代码&#xff08;Python3&#xff09; class Solution:def searchMatrix(self, matrix: List[List[int]], target: int) -> boo…

生成JavaDoc文档

生成 JavaDoc 文档 1、快速生成 文档 注解 2、常见的文档注解 3、脚本生成 doc 文档 4、IDEA工具栏生成 doc 文档 第一章 快速入门 第01节 使用插件 在插件工具当中&#xff0c;找到插件 javaDoc 使用方式&#xff0c;在代码区域&#xff0c;直接点击右键。选择 第02节 常用注…