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

文章目录

    • 一、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.pswp.cn/news/908049.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《对象创建的秘密:Java 内存布局、逃逸分析与 TLAB 优化详解》

大家好呀&#xff01;今天我们来聊聊Java世界里那些"看不见摸不着"但又超级重要的东西——对象在内存里是怎么"住"的&#xff0c;以及JVM这个"超级管家"是怎么帮我们优化管理的。放心&#xff0c;我会用最接地气的方式讲解&#xff0c;保证连小学…

简单实现Ajax基础应用

Ajax不是一种技术&#xff0c;而是一个编程概念。HTML 和 CSS 可以组合使用来标记和设置信息样式。JavaScript 可以修改网页以动态显示&#xff0c;并允许用户与新信息进行交互。内置的 XMLHttpRequest 对象用于在网页上执行 Ajax&#xff0c;允许网站将内容加载到屏幕上而无需…

详解开漏输出和推挽输出

开漏输出和推挽输出 以上是 GPIO 配置为输出时的内部示意图&#xff0c;我们要关注的其实就是这两个 MOS 管的开关状态&#xff0c;可以组合出四种状态&#xff1a; 两个 MOS 管都关闭时&#xff0c;输出处于一个浮空状态&#xff0c;此时他对其他点的电阻是无穷大的&#xff…

Matlab实现LSTM-SVM回归预测,作者:机器学习之心

Matlab实现LSTM-SVM回归预测&#xff0c;作者&#xff1a;机器学习之心 目录 Matlab实现LSTM-SVM回归预测&#xff0c;作者&#xff1a;机器学习之心效果一览基本介绍程序设计参考资料 效果一览 基本介绍 代码主要功能 该代码实现了一个LSTM-SVM回归预测模型&#xff0c;核心流…

Leetcode - 周赛 452

目录 一&#xff0c;3566. 等积子集的划分方案二&#xff0c;3567. 子矩阵的最小绝对差三&#xff0c;3568. 清理教室的最少移动四&#xff0c;3569. 分割数组后不同质数的最大数目 一&#xff0c;3566. 等积子集的划分方案 题目列表 本题有两种做法&#xff0c;dfs 选或不选…

【FAQ】HarmonyOS SDK 闭源开放能力 —Account Kit(5)

1.问题描述&#xff1a; 集成华为一键登录的LoginWithHuaweiIDButton&#xff0c; 但是Button默认名字叫 “华为账号一键登录”&#xff0c;太长无法显示&#xff0c;能否简写成“一键登录”与其他端一致&#xff1f; 解决方案&#xff1a; 问题分两个场景&#xff1a; 一、…

Asp.Net Core SignalR的分布式部署

文章目录 前言一、核心二、解决方案架构三、实现方案1.使用 Azure SignalR Service2.Redis Backplane(Redis 背板方案&#xff09;3.负载均衡配置粘性会话要求无粘性会话方案&#xff08;仅WebSockets&#xff09;完整部署示例&#xff08;Redis Docker&#xff09;性能优化技…

L2-054 三点共线 - java

L2-054 三点共线 语言时间限制内存限制代码长度限制栈限制Java (javac)2600 ms512 MB16KB8192 KBPython (python3)2000 ms256 MB16KB8192 KB其他编译器2000 ms64 MB16KB8192 KB 题目描述&#xff1a; 给定平面上 n n n 个点的坐标 ( x _ i , y _ i ) ( i 1 , ⋯ , n ) (x\_i…

【 java 基础知识 第一篇 】

目录 1.概念 1.1.java的特定有哪些&#xff1f; 1.2.java有哪些优势哪些劣势&#xff1f; 1.3.java为什么可以跨平台&#xff1f; 1.4JVM,JDK,JRE它们有什么区别&#xff1f; 1.5.编译型语言与解释型语言的区别&#xff1f; 2.数据类型 2.1.long与int类型可以互转吗&…

高效背诵英语四级范文

以下是结合认知科学和实战验证的 ​​高效背诵英语作文五步法​​&#xff0c;助你在30分钟内牢固记忆一篇作文&#xff0c;特别适配考前冲刺场景&#xff1a; &#x1f4dd; ​​一、解构作文&#xff08;5分钟&#xff09;​​ ​​拆解逻辑框架​​ 用荧光笔标出&#xff…

RHEL7安装教程

RHEL7安装教程 下载RHEL7镜像 通过网盘分享的文件&#xff1a;RHEL 7.zip 链接: https://pan.baidu.com/s/1ExLhdJigj-tcrHJxIca5XA?pwdjrrj 提取码: jrrj --来自百度网盘超级会员v6的分享安装 1.打开VMware&#xff0c;新建虚拟机&#xff0c;选择自定义然后下一步 2.点击…

结构型设计模式之Decorator(装饰器)

结构型设计模式之Decorator&#xff08;装饰器&#xff09; 前言&#xff1a; 本案例通过李四举例&#xff0c;不改变源代码的情况下 对“才艺”进行增强。 摘要&#xff1a; 摘要&#xff1a; 装饰器模式是一种结构型设计模式&#xff0c;允许动态地为对象添加功能而不改变其…

Kotlin委托机制使用方式和原理

目录 类委托属性委托简单的实现属性委托Kotlin标准库中提供的几个委托延迟属性LazyLazy委托参数可观察属性Observable委托vetoable委托属性储存在Map中 实践方式双击back退出Fragment/Activity传参ViewBinding和委托 类委托 类委托有点类似于Java中的代理模式 interface Base…

SpringBoot接入Kimi实践记录轻松上手

kimi简单使用 什么是Kimi API 官网&#xff1a;https://platform.moonshot.cn/ Kimi API 并不是一个我所熟知的广泛通用的术语。我的推测是&#xff0c;你可能想问的是关于 API 的一些基础知识。API&#xff08;Application Programming Interface&#xff0c;应用程序编程接…

书籍在其他数都出现k次的数组中找到只出现一次的数(7)0603

题目 给定一个整型数组arr和一个大于1的整数k。已知arr中只有1个数出现了1次&#xff0c;其他的数都出现了k次&#xff0c;请返回只出现了1次的数。 解答&#xff1a; 对此题进行思路转换&#xff0c;可以将此题&#xff0c;转换成k进制数。 k进制的两个数c和d&#xff0c;…

React 项目初始化与搭建指南

React 项目初始化有多种方式&#xff0c;可以选择已有的脚手架工具快速创建项目&#xff0c;也可以自定义项目结构并使用构建工具实现项目的构建打包流程。 1. 脚手架方案 1.1. Vite 通过 Vite 创建 React 项目非常简单&#xff0c;只需一行命令即可完成。Vite 的工程初始化…

大模型模型推理的成本过高,如何进行量化或蒸馏优化

在人工智能的浪潮中,大模型已经成为推动技术革新的核心引擎。从自然语言处理到图像生成,再到复杂的多模态任务,像GPT、BERT、T5这样的庞大模型展现出了惊人的能力。它们在翻译、对话系统、内容生成等领域大放异彩,甚至在医疗、金融等行业中也开始扮演重要角色。可以说,这些…

机器学习在多介质环境中多污染物空间预测的应用研究

机器学习在多介质环境中多污染物空间预测的应用研究 1. 引言 1.1 研究背景与意义 随着工业化和城市化进程加速,环境中多种污染物的共存已成为全球性环境问题。重金属(如铅、汞、镉)、有机污染物(如多环芳烃、农药残留)和新兴污染物(如微塑料、药品残留)在空气、水体、…

图解深度学习 - 激活函数和损失函数

激活函数和损失函数在深度学习中扮演着至关重要的角色。通过选择合适的激活函数和损失函数&#xff0c;可以显著提高神经网络的表达能力和优化效果。 其中激活函数是神经网络中的非线性函数&#xff0c;用于在神经元之间引入非线性关系&#xff0c;从而使模型能够学习和表示复…

影响服务器稳定性的因素都有什么?

服务器的稳定性会影响到业务是否能够持续运行&#xff0c;用户在进行访问网站的过程中是否出现页面卡顿的情况&#xff0c;本文就来了解一下都是哪些因素影响着服务器的稳定性。 服务器中的硬件设备是保证服务器稳定运行的基础&#xff0c;企业选择高性能的处理器和大容量且高速…