学懂C++(三十四):深入详解 C++ 高级多线程编程技术中的并发设计模式
引言
在现代软件开发中,多线程编程已成为提升性能和响应能力的重要手段。设计模式为解决并发问题提供了有效的解决方案。本文将探讨常见的并发设计模式,包括生产者-消费者模式、读者-写者模式、单例模式、帧-工作者模式以及Future-Task模式,并为每个示例提供详细的注释和解析,以帮助读者更好地理解其实现原理。
1. 生产者-消费者模式
1.1 概念
生产者-消费者模式涉及两个角色:生产者和消费者。生产者负责生成数据并将其放入缓冲区,而消费者则从缓冲区中取出数据进行处理。此模式的关键在于协调生产者和消费者之间的工作,以避免竞态条件和资源浪费。
1.2 示例及解析
以下是一个简单的生产者-消费者模式的实现:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>// 定义一个线程安全的缓冲区
std::queue<int> buffer; // 用于存放生产的数据
const unsigned int maxBufferSize = 5; // 定义缓冲区的最大大小
std::mutex mtx; // 用于保护缓冲区的互斥锁
std::condition_variable cv; // 用于条件变量通知// 生产者线程函数
void producer(int id) {for (int i = 0; i < 10; ++i) {std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产过程std::unique_lock<std::mutex> lock(mtx); // 锁定缓冲区// 等待缓冲区有空间cv.wait(lock, [] { return buffer.size() < maxBufferSize; });buffer.push(i); // 生产数据std::cout << "Producer " << id << " produced: " << i << std::endl;cv.notify_all(); // 通知消费者有新数据可用}
}// 消费者线程函数
void consumer(int id) {for (int i = 0; i < 10; ++i) {std::this_thread::sleep_for(std::chrono::milliseconds(150)); // 模拟消费过程std::unique_lock<std::mutex> lock(mtx); // 锁定缓冲区// 等待缓冲区有数据cv.wait(lock, [] { return !buffer.empty(); });int value = buffer.front(); // 取出数据buffer.pop(); // 移除数据std::cout << "Consumer " << id << " consumed: " << value << std::endl;cv.notify_all(); // 通知生产者有空间可以生产}
}int main() {std::thread p1(producer, 1); // 创建生产者线程std::thread c1(consumer, 1); // 创建消费者线程p1.join(); // 等待生产者线程结束c1.join(); // 等待消费者线程结束return 0;
}
1.3 运行结果分析
在这个示例中,生产者和消费者线程通过互斥锁和条件变量进行同步,确保了对共享缓冲区的安全访问。生产者在缓冲区满时等待,而消费者在缓冲区空时等待。
2. 读者-写者模式
2.1 概念
读者-写者模式允许多个读者并行访问资源,但在写者访问资源时,所有读者和其他写者必须被阻塞。这种模式适用于读操作远远多于写操作的场景。
2.2 示例及解析
以下是一个简单的读者-写者模式的实现:
#include <iostream>
#include <thread>
#include <mutex>
#include <shared_mutex>
#include <chrono>std::shared_mutex rwMutex; // 读写锁
int sharedData = 0; // 共享数据// 读者线程函数
void reader(int id) {for (int i = 0; i < 5; ++i) {std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟读操作rwMutex.lock_shared(); // 获取共享锁std::cout << "Reader " << id << " read: " << sharedData << std::endl; // 输出共享数据rwMutex.unlock_shared(); // 释放共享锁}
}// 写者线程函数
void writer(int id) {for (int i = 0; i < 5; ++i) {std::this_thread::sleep_for(std::chrono::milliseconds(200)); // 模拟写操作rwMutex.lock(); // 获取独占锁sharedData++; // 修改共享数据std::cout << "Writer " << id << " wrote: " << sharedData << std::endl; // 输出更新后的数据rwMutex.unlock(); // 释放独占锁}
}int main() {std::thread r1(reader, 1); // 创建读者线程std::thread r2(reader, 2); // 创建另一个读者线程std::thread w1(writer, 1); // 创建写者线程r1.join(); // 等待读者线程结束r2.join(); // 等待读者线程结束w1.join(); // 等待写者线程结束return 0;
}
2.3 运行结果分析
在这个示例中,多个读者可以同时读取数据,而在写者写入数据时,读者会被阻塞,提高了性能。
3. 单例模式
3.1 概念
单例模式确保一个类仅有一个实例,并提供全局访问点。在线程安全的上下文中,特别需要确保在多线程环境下创建实例的安全性。
3.2 示例及解析
以下是一个线程安全的单例模式实现示例:
#include <iostream>
#include <mutex>// 单例类定义
class Singleton {
public:// 获取单例实例static Singleton& getInstance() {std::call_once(initFlag, []() { // 确保单例只被初始化一次instance.reset(new Singleton());});return *instance; // 返回单例实例}void displayMessage() {std::cout << "Singleton Instance Address: " << this << std::endl; // 输出实例地址}private:Singleton() = default; // 私有构造函数static std::unique_ptr<Singleton> instance; // 存储单例实例的智能指针static std::once_flag initFlag; // 一次性标志
};// 静态成员初始化
std::unique_ptr<Singleton> Singleton::instance;
std::once_flag Singleton::initFlag;int main() {std::thread t1([]() { Singleton::getInstance().displayMessage(); });std::thread t2([]() { Singleton::getInstance().displayMessage(); });t1.join(); // 等待线程t1结束t2.join(); // 等待线程t2结束return 0;
}
3.3 运行结果分析
运行结果显示无论创建多少线程,单例实例只会创建一个,保证了线程安全。
4. 帧-工作者模式
4.1 概念
帧-工作者模式是一种常见的并发设计模式,适用于需要将大量任务分配给多个工作线程处理的场景。通过将任务分成较小的块,将这些块分配给多个工作线程,可以实现高效的并发处理。
4.2 示例及解析
以下是帧-工作者模式的简单实现:
#include <iostream>
#include <vector>
#include <thread>
#include <mutex>std::mutex mtx; // 用于保护输出的互斥锁// 工作线程函数
void worker(int id, int start, int end) {for (int i = start; i < end; ++i) {std::this_thread::sleep_for(std::chrono::milliseconds(50)); // 模拟工作std::lock_guard<std::mutex> lock(mtx); // 锁定互斥锁std::cout << "Worker " << id << " processing: " << i << std::endl; // 输出处理信息}
}int main() {const int totalWork = 20; // 总工作量const int numWorkers = 4; // 工作线程数量std::vector<std::thread> workers; // 存储线程的向量int workPerThread = totalWork / numWorkers; // 每个线程处理的工作量for (int i = 0; i < numWorkers; ++i) {// 创建工作线程,分配不同的任务块workers.emplace_back(worker, i + 1, i * workPerThread, (i + 1) * workPerThread);}for (auto& worker : workers) {worker.join(); // 等待所有工作线程结束}return 0;
}
4.3 运行结果分析
在该示例中,多个工作线程可以并行处理不同的任务块,提升了效率。
5. Future-Task 模式
5.1 概念
Future-Task模式是一种用于异步编程的设计模式,允许任务在后台异步执行,并在需要时获取结果。这种模式通常与线程池结合使用,以提高资源利用率。
5.2 示例及解析
以下是 Future-Task 模式的实现示例:
#include <iostream>
#include <future>
#include <thread>
#include <chrono>// 异步任务
int asyncTask(int id) {std::this_thread::sleep_for(std::chrono::milliseconds(100 * id)); // 模拟耗时任务return id * 2; // 返回计算结果
}int main() {// 使用 std::async 启动异步任务std::future<int> fut1 = std::async(std::launch::async, asyncTask, 1);std::future<int> fut2 = std::async(std::launch::async, asyncTask, 2);// 获取结果并输出std::cout << "Result from task 1: " << fut1.get() << std::endl; // 阻塞等待 task 1 完成std::cout << "Result from task 2: " << fut2.get() << std::endl; // 阻塞等待 task 2 完成return 0;
}
5.3 运行结果分析
在这个示例中,两个任务在后台异步执行,主线程可以在等待结果的同时继续处理其他逻辑。
6. 核心点总结与技术精髓
6.1 设计模式的核心点
- 生产者-消费者模式:利用互斥锁和条件变量保证缓冲区的安全访问,避免竞争条件。
- 读者-写者模式:允许多个读者并发访问,确保写者访问时的独占性,适用读多写少的场景。
- 单例模式:确保全局只有一个实例,并提供线程安全的访问方式,适合需要共享资源的情况。
- 帧-工作者模式:将工作分配给多个线程,提升处理效率,适合大规模并行处理。
- Future-Task模式:支持异步执行和结果获取,通过将任务放入后台执行来优化资源利用率。
7. 结语
掌握并发设计模式是实现高效、多线程程序的关键。通过合理的设计和实现,我们能够有效地解决并发问题,提高程序的性能和可靠性。希望本文能够为您在 C++ 的多线程编程中提供有价值的参考和指导。
上一篇:学懂C++(三十三):深入详解 C++ 高级多线程编程技术中的并发数据结构
下一篇:学懂C++(三十五):深入详解C++ 多线程编程性能优化
相关文章:
学懂C++(三十四):深入详解 C++ 高级多线程编程技术中的并发设计模式
引言 在现代软件开发中,多线程编程已成为提升性能和响应能力的重要手段。设计模式为解决并发问题提供了有效的解决方案。本文将探讨常见的并发设计模式,包括生产者-消费者模式、读者-写者模式、单例模式、帧-工作者模式以及Future-Task模式,并…...
大数据产业链图谱_产业链全景图_大数据行业市场分析
数据作为新型生产要素,是数字化、网络化、智能化的基础,已快速融入生产、分配、流通、消费和社会服务管理等各环节,影响着千行百业,推动着我国数字经济的蓬勃发展。 大数据又称巨量数据、海量数据,是由数量巨大、结构…...
photonserver 部署相关教程
Photon Server 是 Exit Games 开发的高性能、可扩展的多人游戏服务器框架。部署 Photon Server 需要一些基础的服务器管理知识和配置技巧。以下是一个基本的部署教程,帮助你将 Photon Server 部署在 Windows 服务器上。 目录 1. 下载并安装 Photon Server 2. 配置…...
GEE训练:sentinel-1数据的投影、显示和导出
函数 projection() Returns the default projection of an Image. Throws an error if the bands of the image dont all have the same projection. 返回图像的默认投影。如果图像带的投影不一致,则会抛出错误。 Arguments: this:image (Image): The image from which …...
后端学习笔记(七)--MyBatis参数传递
5.MyBatis参数传递 *MyBatis接口方法中可以接收各种各样的参数,MyBatis底层对于这些参数进行不同的封装处理方式 *单个参数: 1.POJO类型:直接使用,属性名和参数占位符名称一致 2.Map集合:直接使用,…...
uniapp 网络请求自动处理loading
文章目录 背景整理思路V1版本V2版本V3版本 背景 最近在写uniapp,发现执行网络请求的时候经常要处理Loading效果。 比如,在发送网络请求之前,触发Loadng;无论请求成功还是失败都要关闭Loading;请求失败的时候我们还要…...
【Solidity】函数的使用
构造函数 构造函数仅在部署合约时调用一次,它的作用主要是初始化一些状态变量。 contract Demo {address public owner;uint public num;constructor(uint _num) {owner msg.sender;num _num;} }函数装饰器 函数装饰器可以在函数执行之前或之后插入代码逻辑&am…...
详解golang内存管理
介绍 要搞明白 Go 语言的内存管理,就必须先理解操作系统以及机器硬件是如何管理内存的。因为 Go 语言的内部机制是建立在这个基础之上的,它的设计,本质上就是尽可能的会发挥操作系统层面的优势,而避开导致低效情况。 操作系统内存管理 其实现在计算机内存管理的方式都是…...
C++ 线程 一些同步方式
C 线程一些同步方式 1.互斥锁(Mutex)2. 读写锁(Reader-Writer Lock)3. 信号量(Semaphore)4. 原子操作(Atomic)5. 屏障(Barrier)6. 条件变量(Condi…...
【开发语言】编译型语言和解释性语言有啥区别?
作为一名从业多年的程序员,对于编译型语言和解释型语言之间的区别有着深入的理解。这两种类型的编程语言在将源代码转换成可执行代码的过程中采用了不同的机制,这导致了它们在执行效率、跨平台性、安全性以及开发效率等方面存在一些差异。 编译型语言(Compiled Languages)…...
将A服务器上指定文件夹中的文件,批量同步到B服务器上
需求:最近有一个需求,需要定期将A服务器上的PDF文件,同步到B服务器上,于是便写个脚本记录一下! 下面是使用Python3脚本实现的方法 import os import paramikodef copy_pdf_files(source_ip, source_user, source_pas…...
2024.8.17
130124202408171002 DATE #:20240817 ITEM #:DOC WEEK #:SATURDAY DAIL #:捌月拾肆 TAGS < BGM "快哉风 -- 黄金玉米王" > < theme oi-language > < theme oi-graph theory > < [空] > < [空] >取次花丛懒回顾,半缘修道…...
十分钟搭建一个RTMP服务器
使用SRS搭建RTMP服务器 如果您需要搭建一个RTMP服务器,您可以使用SRS(Simple-RTMP-Server)来完成此任务。SRS是一个开源的RTMP服务器下面是一个简单的步骤指南: 获取srs srs官⽹:https://github.com/ossrs/srs 码云…...
Spring Boot解决循环注入问题
Spring Boot解决循环依赖注入问题 代码问题回显启动错误日志解决方案:使用事件驱动或通过 ApplicationContext 手动获取 Bean1. 事件驱动设计2. 使用 ApplicationContext 手动获取 Bean3. 拆分逻辑 总结 代码问题回显 现有代码1 在InterestService中依赖MemberInte…...
《数据挖掘》期末考核重点
1.数据预处理的目的与形式 数据预处理的目的是提供干净,简洁,准确的数据,以达到简化模型和提高算法泛化能力的目的,使挖掘过程更有效,更容易,提高挖掘效率和准确性。 2.数据预处理的形式 数据清理&#…...
Golang | Leetcode Golang题解之第334题递增的三元子序列
题目: 题解: func increasingTriplet(nums []int) bool {n : len(nums)if n < 3 {return false}first, second : nums[0], math.MaxInt32for i : 1; i < n; i {num : nums[i]if num > second {return true} else if num > first {second n…...
HarmonyOs编写一个案例实现一个照片选择(阶段进阶 四种需求 逐一完善)
需求1. .实现照片选择 并将选择好的照片展示出来 import { GoodItem } from ../06/modules;Entry Component struct PhotoPage {State message: string 实现一个相册;State List: GoodItem[] [{goods_name: dsfjlsjkfsf,goods_price: 100,goods_img: https://img1.baidu.com…...
洗衣机洗衣服一些知识
01智能:按衣物多少自动调节合适水位的标准洗涤程序 (需要30分钟时间) 02:大物:较大,较厚的衣服洗涤 03:轻柔:毛织品或内衣洗涤 04:快速:少量清污衣服洗涤 (13分钟) 05:浸泡:先浸泡一段时间再洗涤 06:单洗:只洗衣不脱水 07:单脱:只脱水不洗衣 08:洁桶:清洁洗衣桶 准备工作: (1)…...
探索文件系统:高效、可靠的文件管理与访问机制
文件系统的功能规划 内存就像是一个书包,容量有限,只能带着一部分东西。而图书馆则是一个专门存储和管理文件的地方,拥有更大的容量,并且可以永久保存文件。为了能够快速找到需要的文件,我们需要有一个书单来记录每本…...
启程与远征Ⅸ--优化生成式人工智能以满足业务需求的框架
生成类似人类的文本和语音曾经只存在于科幻小说中。但 GPT-3 和 PaLM 等大型语言模型 (LLM) 的快速发展让这一愿景更接近现实,解锁了从聊天机器人到内容创作等一系列有前景的商业应用。 然而,通用基础模型往往无法满足行业用例的需求。企业对其生成式 A…...
大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
