多线程程序设计(五)——Producer-Consumer

news/2025/3/31 17:23:38/文章来源:https://www.cnblogs.com/green-cnblogs/p/18795973

本文摘要了《Java多线程设计模式》一书中提及的 Producer-Consumer 模式的适用场景,并针对书中例子(若干名称有微调)给出一份 C++ 参考实现及其 UML 逻辑图,也列出与之相关的模式。

◆ 适用场景

为了匹配数据的生产者(Producer)线程与消费者(Consumer)线程之间的处理速度。提高整体吞吐量(throughput)的同时,并保证数据的安全性。

◆ 解决方案

在 Producer 与 Consumer 线程之间加上一个可存放多条数据的缓冲。使用 std::mutex 锁机制保证对缓冲的临界区的互斥访问;使用 std::condition_variable 对象让线程等待至缓冲区可访问,并在可访问后唤醒在等待中的线程。

◆ 参考实现

例子模拟了厨师把做好的蛋糕给食客吃过程。厨师(Maker)把做好的蛋糕放在桌子(Table)上,食客(Eater)从桌上拿蛋糕吃。桌上最多放 3 块蛋糕,如果桌上还有 3 块蛋糕,厨师要等到桌上有空后才能接着放;如果桌上没有蛋糕了,食客要等到桌上有蛋糕后才能接着拿。

class Table
{...vector<string>__buffer__;                             #1intconst__capacity__;int__head__;int__tail__;int__count__;mutex__mtx__;condition_variable__cv__;...voidput(string cake, string name){unique_lock<mutex> lk(__mtx__);std::printf("%s puts %s\n", name.c_str(), cake.c_str());__cv__.wait(lk,                                               #3[&] {bool full = __count__ == __capacity__;          #2if (full)std::printf("Table is full. %s is waiting...\n", name.c_str());return !full;});__buffer__[__tail__] = cake;                        #4__tail__ = (__tail__ + 1) % __capacity__;++__count__;__cv__.notify_all();                                    #5}stringtake(string name){unique_lock<mutex> lk(__mtx__);__cv__.wait(lk,                                         #7[&] {bool idle = __count__ == 0;                 #6if (idle)std::printf("Table is empty. %s is waiting...\n", name.c_str());return !idle;});string cake(__buffer__[__head__]);                  #8__head__ = (__head__ + 1) % __capacity__;--__count__;__cv__.notify_all();                                    #9std::printf("%s takes %s\n", name.c_str(), cake.c_str());return cake;}};

存放蛋糕(string)的缓冲区(#1)由 Table 管理。做好的蛋糕通过 put 函数被放入缓冲区中。当蛋糕被放到桌上前,要判断缓冲区内是否已满(#2)。如果已放满(full 为 true),做蛋糕的厨师要等待(#3);如果未放满,蛋糕被放入缓冲区后(#4),并会唤醒等待的其他厨师或食客(#5)。缓冲区中的蛋糕通过 take 函数被取出。当蛋糕被取出前,要判断缓冲区内是否还有蛋糕(#6)。如果没有蛋糕(idle 为 true),取蛋糕的食客要等待(#7);如果有蛋糕,蛋糕从缓冲区中被取出(#8),并会唤醒等待的其他厨师或食客(#9)。

class Maker
{...string__name__;...voidrun(Table & table){while (true) {std::this_thread::sleep_for(milliseconds(std::rand()%1000));...cake += ...table.put(cake, __name__);                      #1}}};class Eater
{...string__name__;...voidrun(Table & table){while (true) {string cake = table.take(__name__);                 #2std::this_thread::sleep_for(milliseconds(std::rand()%3000));}}};

厨师会在 0 ~ 1 秒时间内,将做好的蛋糕放到桌上(#1)。食客会在 0 ~ 3 秒时间内,将做好的蛋糕从桌上拿走(#2)。

以下类图展现了代码主要逻辑结构,

class

以下顺序图展现了线程并发中的交互。

sequence

◆ 验证测试

笔者在实验环境一中编译代码(-std=c++11)成功后运行可执行文件,

$ g++ -std=c++11 -lpthread producer_consumer.cpp
$ ./a.out

运行结果如下:

...
Table is empty. Eater-1 is waiting...
Table is empty. Eater-2 is waiting...
Table is empty. Eater-3 is waiting...
Maker-3 puts [Cake No.1 by Maker-3]
Eater-1 takes [Cake No.1 by Maker-3]
Table is empty. Eater-2 is waiting...
Table is empty. Eater-3 is waiting...
Maker-2 puts [Cake No.2 by Maker-2]
Eater-2 takes [Cake No.2 by Maker-2]
Table is empty. Eater-3 is waiting...
Maker-3 puts [Cake No.3 by Maker-3]
Eater-3 takes [Cake No.3 by Maker-3]
Table is empty. Eater-2 is waiting...
Maker-1 puts [Cake No.4 by Maker-1]
Eater-2 takes [Cake No.4 by Maker-1]
Maker-1 puts [Cake No.5 by Maker-1]
Maker-3 puts [Cake No.6 by Maker-3]
Maker-2 puts [Cake No.7 by Maker-2]
Maker-3 puts [Cake No.8 by Maker-3]
Table is full. Maker-3 is waiting...
Maker-2 puts [Cake No.9 by Maker-2]
Table is full. Maker-2 is waiting...
Eater-3 takes [Cake No.5 by Maker-1]
Table is full. Maker-2 is waiting...
Maker-1 puts [Cake No.10 by Maker-1]
...

可以看到,当 Table 上没有蛋糕时,食客需要等待;而当 Table 上已放满蛋糕时,厨师需要等待。

◆ 相关模式

  • 在缓冲中存取数据的时候,使用了 Guarded Suspension 模式。
  • Future 模式在传递返回值时,使用了 Producer-Consumer 模式。
  • Worker 模式在传递请求时,使用了 Producer-Consumer 模式。

◆ 最后

完整的代码请参考 [gitee] cnblogs/18795973 。

致《Java多线程设计模式》的作者结城浩。写作中也参考了《C++并发编程实战》中的若干建议,致作者 Anthony Williams 和译者周全等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/907283.html

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

相关文章

Google Chrome AI innovations Al In One

Google Chrome AI innovations Al In OneGoogle Chrome AI innovations Al In OneConsole Insights & AI assistance通过控制台数据分析更好地了解错误和警告https://goo.gle/devtools-console-messages-ai https://developer.chrome.com/docs/devtools/console/understand…

【Java开发 】mcp server实战

Model Context Protocol(MCP)作为新一代 AI 服务交互协议,近期发布了 Java SDK 0.8 版本。 虽然新版本带来了更强大的会话管理能力和工具集成机制,但直接使用原生 SDK 仍存在以下挑战:需要手动处理依赖注入和生命周期管理工具注册流程较为复杂缺乏与现有 Spring 生态的深度…

T检验(Students T-test), Wilcoxon符号秩检验,Mann-Whitney U检验

1.基本概念 T检验 是一种统计学方法,用于判断两组数据的均值是否存在显著差异 。其核心思想是通过计算“t值”和对应的“p值”,评估观察到的差异是否由随机误差引起,还是反映了真实的生物学或实验效应。 适用场景 :比较两组独立样本的均值(如对照组 vs 实验组)。 检验单组…

写了 8 年C++,才知道this指针竟是这样工作的!从汇编看本质!

大家好,我是小康。今天我们来聊聊 C++ 的 this 指针。 相信我,看完这篇文章,你将彻底搞懂 C++ 中最神秘的 this 指针!不再被面试官问到 this 时一脸茫然! 前言:this指针,C++中的隐形杀手 嘿,朋友们!还记得第一次接触 C++ 的 this 指针时的懵逼感觉吗? "为啥要用…

Redis 过期键删除和内存淘汰策略【Redis 系列之四】

本文主要介绍了 Redis 过期键删除和内存淘汰策略,仅供参考。〇、前言 对于 Redis 服务器来说,内存资源非常宝贵,如果一些过期键一直不被删除,就会造成资源浪费。 那么,本文将结合博主收集的资料,简单介绍下过期键删除、内存淘汰两个策略,仅供参考。 博主 Redis 相关文章…

2025最新面试题-mysql面试题(三)

事务的四大特性 A账户 10000 -2000 8000+2000=10000 8000+2000 写入buffer Pool(内存缓冲池) Redo Log 环形日志 磁盘 B账户 5000 +2000 7000 原子性(Atomicity) 也就是我们刚才说的不可再分,也就意味着我们对数据库的一系列的操作,要么都是成功,要么都是失败,不可能出…

【MCP协议】你需要了解的 AI 集成突破

了解 MCP 如何重塑 AI 与外部数据源交互的能力。 MCP——是不是有点懵?这也是我的第一反应。我最近才听说它,发现大多数人甚至还不了解它。起初,我也感到困惑,以为这不过是又一个AI领域的流行词。但随着深入了解,我发现MCP并非昙花一现的潮流,而是真正解决了一个长期困扰…

卧槽!C 语言宏定义原来可以玩出这些花样?高手必看!

大家好啊!我是小康。 今天我们来聊一个听起来枯燥但实际上暗藏玄机的话题 —— C 语言的宏定义。 啥?宏定义?那不就是个简单的替换工具吗? 兄dei,如果你也是这么想的,那可就大错特错了!宏定义在 C 语言里简直就是个变形金刚,看似普通,实则暗藏神通。今天我们就来扒一扒…

TapData Oracle 日志解析性能全面领先:20秒处理1GB日志,效率提升100% ——释放数据潜能,驱动实时决策

TapData Oracle日志解析性能全面领先!实测1GB日志解析仅需20秒,效率超竞品2-8倍,降低50%硬件成本。立即了解金融、电商等行业高效数据处理方案。在当今数据驱动的时代,企业对于数据库日志解析的速度和效率要求越来越高。面对不断增长的数据量和实时分析需求,TapData 凭借技…

瑞芯微RK356X主板复用接口配置方法,触觉智能嵌入式方案商

本文介绍瑞芯微RK356X系列复用接口配置的方法,基于触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。 复用接口介绍 由下图可知,红圈内容当前引脚可配置为SPI0…

团队项目第二周作业

需求规格说明书 一、面向用户分析 网上点餐系统主要面向以下用户群体: 普通消费者:包括年轻人、上班族、学生等,他们希望通过便捷的方式快速点餐。 餐厅经营者:需要通过系统管理菜品、订单、顾客信息等,以提高运营效率。 外卖配送人员:负责将订单配送到消费者手中,系统需…

解惑:采购时亚克力板尺寸一般有多少?-郑州亚克力制品代加工-郑州水晶字logo代加工-亚克力切割雕刻-外协加工-委外加工-激光代加工-河南郑州-芯晨微纳(河南)

亚克力板的常规尺寸因生产厂家、用途和工艺(如挤出板或浇铸板)而有所不同,以下是常见的规格参考:厚度范围挤出板:通常为 1mm–10mm,部分厂家可生产更厚(如12mm、15mm)。 浇铸板:厚度范围更广,常见 1mm–50mm,特殊需求可定制更厚板材。常见标准厚度(单位:mm): 1、…