【最佳实践】生产者和消费者模式中的双缓冲技术
【这篇文章说了啥】
这篇文章主要介绍了在生产者-消费者模式中,生产和消费之间有大量数据需要交互时的一个高效率的解决方案。
【问题引入】
1. 问题场景
在设计模式中,生产者-消费者模式肯定是排在前面位置的,在实际开发过程中,也常常需要使用这个模式。
在讲解设计模式的书籍中,只会从抽象的角度对生产者-消费者模式进行讲解。面对实际的编程问题,还需要具体问题具体分析。
这里给出一个使用场景,你可以停下来思考一下该如何解决问题:
你需要实现一个日志库,每一个线程调用日志库API函数来写入日志信息,所有的日志信息需要持久化到本地的文件系统。
咱们来分析一下,有哪些问题需要解决:
(1)多线程调用日志库的API,所以API函数必须是线程安全的。
(2)日志信息无法预计频率,要考虑波峰和波谷。
(3)日志信息需要持久化到本地文件系统中,涉及到文件操作,而文件的IO操作速度相对于CPU来说是很慢的。
(4)每一条日志信息不应该是立刻写入到文件,而是应该缓存在内存中,当数据量积累到一定的大小再写入到文件(当然,也要考虑定时写入文件)。
2. 解决思路
咱们再回到生产者-消费者模式上。书籍上在介绍这种模式时,一般都是同步模式,即:
生产者产生一个数据后通知消费者,然后等待数据被“消费”;
消费者收到生产者的通知后,“消费”数据,然后再通知生产者继续生产。
生产和消费交替执行,所以我称之为同步模式。
但是,在上面所说的日志系统中,显然不能用同步模式。因为日志的产生速度与写文件速度是无法预估的,例如:在某个场景下日志的产生速度非常快,而在另一个场景下日志的产生速度特别慢。
对于这样的需求,生产者(日志的产生)和消费者(把日志写入文件)速度不匹配,显然应该使用不同的线程来执行。此时,你是不是立刻想到使用消息队列来进行数据缓冲,不就解决了这个速度不匹配的问题?也就是这样:
生产者:往队列的头部插入日志信息(入队)
消费者:从队列的尾部读取日志信息(出队)
当然,你还考虑到因为他们是不同的线程,在操作同一个队列时,需要用一个锁(Mutex)来保护消息队列。大概就是这个样子:
看起来很完美,但是有一个问题:消费者从消息队列每读取一条日志信息就,写入文件系统,但是写文件操作是很耗时的。频繁的从消息队列中获取数据,而且每次都要上锁,一定会对生产者的写日志效率产生影响,因为生产者也要对消息队列上锁才能把日志信息插入队列的头部,如果此时消息队列正好被消费者锁住了,那么生产者就必须伤心的等待了~~这样就会很大影响到日志系统整体的吞吐率。
3. 使用双缓冲
既然消费者的写文件速度比较慢,一定不能影响了生产者的写入效率,所以我们可以用两个消息队列来分别存储:正在写入的日志信息,正在读取的日志信息,也就是所谓的“双缓冲”技术。用图画出来就是这个样子:
注意上图中,内存中用来缓存日志的空间不一定要用消息队列,因为日志信息往往都是字符串类型,直接用一块连续的堆或栈的空间来存储就可以了,所以图中就用“缓冲区1”、“缓冲区2”来表示。
在这个模型中,生产者向缓冲区1中写日志信息;而消费者从缓冲区2中读取日志信息,这样的话,消费者的写文件操作无论怎么慢都不会影响到生产者产生日志了。
此时,你肯定要说:缓冲区1和缓冲区2是两个独立的内存空间,当缓冲区1被写满后如何把其中的内容“复制”到缓冲区2中呢?
好问题,这也是日志系统实现高吞吐率的关键地方!
4. 缓冲区交换
最直觉的想法就是在某个时刻(比如:缓冲区1写满了,缓冲区2空了,定时),把缓冲器1中的内容用memcpy或者其他的系统函数,复制到缓冲区2中。 当然在复制数据的过程中需要对这两个缓冲区都上锁,在临界区完成复制或者移动操作,而且这个移动操作要尽可能的快,这样才能对生产者和消费者产生最小的影响。但是如果数据量比较大,移动操作还是比较耗时。
再仔细想想,其实我们需要的不是真正的移动操作,而是有一个地方让生产者存放产生的数据,同样的有一个地方让消费者读取数据,只要达成这个目的就可以了。
还有一个更好的方法,就是直接交换两个缓冲区的地址。我们只需要把生产者在写入数据时的指向缓冲区1的指针重新指向缓冲区2, 把消费者读取数据时指向的缓冲区2的指针重新指向缓冲区1,这样就达到了交换缓冲区的目的了。
交换缓冲区之前:生产者向缓冲区1中写日志,消费者从缓冲区2中读日志。
交换缓冲区之后:生产者向缓冲区2中写日志,消费者从缓冲区1中读日志。
在执行交换操作的时候,也需要对这两个缓冲区上锁。但是在这个临界区操作的是:交换两个指针所指向的缓冲区空间,所以执行速度会非常快。
具体到语言层面,对于C来说就是交换两个4字节的地址,对于C++来说可以利用容器类型的swap函数。
这样画图更好理解:
图中:左侧是执行交换操作之前的样子,右侧是执行交换操作之后的样子。可以看到生产者和消费者在任意时刻操作的都是不同的缓冲区,所以不存在相互影响,而且也达到了快速交换内容的目的。
通过这样的双缓冲技术实现的日志系统,实际测试下来发现,吞吐率比很多开源的日志库要高很多。大家如果有兴趣,可以简单测试一下。
【总结】
写到这里,我想表达的内容基本结束了。
你可能还有其他的一些疑点,比如:什么时候交换缓冲区?写入缓冲区满了之后怎么处理?这些就属于另一个话题了。
在这个实际的使用场景中,通过双缓冲技术,很好地解决了生产者和消费者之间的异步操作和速度不匹配问题,提高了日志系统的整体吞吐率。
【打完收工】
不知道这篇文章是否给你带来小小的帮助?
另外,评论和转发都是免费的哦~~~
我会持续分享嵌入式开发过程中的各种最佳实践,欢迎关注微信公众号:IOT物联网小镇
【最佳实践】生产者和消费者模式中的双缓冲技术相关推荐
- VC绘图中的双缓冲技术
VC绘图中的双缓冲技术 转自:VC 绘图,使用双缓冲技术实现 ********************所有的GDI绘图函数使用的都是逻辑坐标(逻辑范围)******************* **** ...
- java swing双缓冲_java中的双缓冲技术
毕业设计有个远程协助功能,得到对方的屏幕后,老是会闪,很是不爽,今天用java的双缓冲技术解决了.代码如下,本类重写了Swing中的JLabel,当Label重绘时,会默认的调用它的update方法, ...
- Python中的生产者与消费者模式(转载)
利用多线程和队列可以实现生产者消费者模式.该模式通过平衡生产线程和消费线程的工作能力来提高程序整体处理数据的速度. 1.什么是生产者和消费者? 在线程世界里,生产者就是生产数据(或者说发布任务)的线程 ...
- python中的生产者与消费者模式
Queue的说明 对于Queue,在多线程通信之间扮演重要的角色 添加数据到队列中,使用put()方法 从队列中取数据,使用get()方法 判断队列中是否还有数据,使用qsize()方法 生产者消费者 ...
- Java多线模式-Producer-Consumer模式(生产者、消费者模式)
1.场景 有些时候需要两个或两个以上的线程协同工作,每个线程需要使用其他线程产生数据. 2.详细说明 可以把上面的场景抽象成生产者和消费者模式.从消费者的角度:消费者需要消费生产者生成的产品. 从生产 ...
- 多线程通信—生产者和消费者模式
1.队列Queue: 从一个线程向另一个线程发送数据最安全的方式可能就是使用queue库中的队列了.创建一个被多个线程共享的Queue对象,这些线程通过使用put()和get()操作来向队列中添加或者 ...
- python 生产者和消费者模式_Python爬虫:生产者和消费者模式
认识生产者和消费者模式 生产者和消费者是多线程中很常见的一个问题.产生数据的模块,我们称之为生产者,而处理数据的模块,就称为消费者.但是单单只有生产者和消费者显然还是不够的,一般来说,我们还有一个缓冲 ...
- 线程间的协作(2)——生产者与消费者模式
2019独角兽企业重金招聘Python工程师标准>>> 1.何为生产者与消费者 在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程. import java.util. ...
- 生产者与消费者模式(python 版)
1. 队列 先进先出 2. 栈 先进后出 Python的Queue模块中提供了同步的.线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列 ...
- Java并发编程系列18:多线程之生产者和消费者模式_信号灯法(wait/notify通知机制)
1.生产者消费者模式 生产者消费者问题(Producer-consumer problem),也称为有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例.该问题 ...
最新文章
- Xamarin.iOS项目提示error MSB3174:”TargetFrameworkVersion”的值无效
- 后端时间转js时间,主要用于取倒计时
- python获取计算机IP、mac地址、计算机名
- Rails non browser app高级篇-capistrano/daemon部署
- RHEL 6.5 rpm包安装mplyer
- python连接postgresql数据库
- python中find函数运算结果类型_Python 运算符与数据类型
- flask-前端-requests之response对应关系 text
- FusionChart 保存图片
- ASP.NET Razor 视图引擎编程参考
- 2014清华计算机系直博名单,2014年清华大学外校推免名单.pdf
- C#.NET的Linq查询、lambda、委托:Func和Action
- CAN分析仪的特点与功能
- 音频格式处理--SOX
- 科普:ARM的授权方式
- 分组卷积与DW卷积、Residuals与Inverted Residuals、bottleneck与linearbottleneck
- 温度控制直流电机转速
- MySQL 获取所有库名、表名、字段名
- android cocoscreator jsc js 间加解密(六)
- 在线工作坊 | 人工智能之 AI on Azure