概述
最近在写一个数据交互模块,若本侧收到数据后的处理是将其显示到UI中(相对耗时),则当对侧主动密集上报时,会出现一种情况:接收大量数据来不及处理的,这就有了生产者消费者不协调问题。多线程模式下,生产者和消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者生产完数据后不用等待消费者处理,而是直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列中获取数据。阻塞队列就相当于一个缓冲区(作用:解耦、支持并发、支持忙闲不均),平衡生产者和消费者的处理能力。
在并发线程中使用队列缓冲区,有个绕不开的问题:假如生产者和消费者都很勤快,一个主要的问题是关于内存分配的性能开销。这时,环形缓冲区来了,他有较少的内存开辟和释放开销,不过,和线程中的队列缓冲区类似,线程中的环形缓冲区也要考虑线程安全的问题。
消息队列
Windows消息队列 还是回头再去看看深入浅出MFC吧,记得里头有不错的介绍来-
关于锁
pthread_mutex_t 和 pthread_cond_t 配合使用 #
最让我受益的是其流程图的画法,将pthread_cond_wait分解为3步操作,从在让一个跨线程的操作流变的相对很清晰。
pthread_cond_wait必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。阻塞时处于解锁状态。
激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。
信号量 互斥锁 条件变量的区别 # 参考文献
产品型消息队列的深入理解 #
Linux循环消息队列 #
Linux自定义消息队列 #
线程安全消息队列的设计思路 #
使用信号量同步Linux多线程 sem
关于内存拷贝
在进行变长数据消息队列设计的过程中,我一直有个纠结,就是想千方百计的去减少数据拷贝过程,当然这种初衷肯定是好的,不过,此处涉及的内存拷贝过程,有想象中的那么耗时吗 ? 将验证转移到到内存的烦恼1中,最终的测试结论(不权威),在办公PC(i5-4690 8G 3.5Hz),平均大约memcpy 1 M数据量耗时1ms
关于缓冲区
场景概述与分析
在uart/socket通信,从接收缓冲区中读取数据,如果处理线程速率慢,那么缓冲区会满,继而被覆盖,从而造成数据的丢失。先看看socket缓冲区,在linux上其大小可以通过内核(驱动)配置文件来改变;Uart缓冲区也能通过接口来设置。
该章研究的是其上层的消息缓冲,场景如下:从IO缓冲区中读取数据,经过帧协议解析,转存到消息队列中,应用层从消息队列中按条读取并处理(假定该处理较耗时),该消息队列也是一层缓冲区,满了依然会丢失,不管这层缓冲是不是循环队列,还是采取了什么Semaphore同步!这个过程中,我get到的缓冲区有3层:
- I/O缓冲区或者叫系统缓冲区(不确定这么叫是否合理!)
- 传输层经过协议解析后并组装为实际意义的应用数据(不同的数据类型),我们暂且叫其数据交互层缓冲区(下称CommBuffer)
- 步骤2中的数据要分发到不同的地方进行使用(for me hmi-fresh),数据确实是处理过来,是可以进行特殊处理的,如直接抛弃旧数据显示最新数据,如忽略新数据等策略。我们暂且叫这其具体数据缓冲区(下称ConcreteBuffer)
在继续之前,有必要先再了解下,缓冲区意义何在?有篇博客中,讲了系统中默默工作的文件输出流,(写文件后flush你一定用到过(* ̄︶ ̄))谈到了缓冲的意义、产生的问题、什么时候用缓冲及缓冲策略。好了再回归正题!我要实现缓冲区的原因:DEV-B会给DEV-A(我),主动上报数据(相同或不同),而且会有密集上报的情况发生,甚至在晕的会后会持续密集上报,我很纠结啊!
作为一个严谨的程序猿,又在工业环境下,我们禁止数据悄无声息的丢失,就算DEV-B持续密集攻击!数据处理不过来,谁该买账,我的原则是,自己的事情自己做!主要有如下这么几点:
- 数据交互层不应该停下来等待应用层将数据处理完成
- 为应用层不同具体数据定制输出缓冲策略,尤其要注意满了后怎么办 ?
- CommBuffer不能满,其运行在:应用层不进行任何处理时,模块处理能力,允许短暂密集数据出现,或短暂大数据出现!
- ConcreteBuffer允许满,但必须提供处理手段并告知客户,如“做过丢弃处理等”!其运行时支持,对持续密集数据的处理!
环形缓冲区
RingBuf的原理
请补充一套环形缓冲区的运行示意图。
RingBuf安全问题
环形缓冲区通常有一个读指针和一个写指针。读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区中可写的缓冲区。通过移动读指针和写指针就可以实现缓冲区的数据读取和写人。在通常情况下,环形缓冲区的读用户仅仅会影响读指针,而写用户仅仅会影响写指针。如果仅仅有一个读用户和一个写用户,那么不需要添加互斥保护机制就可以保证数据的正确性。如果有多个读写用户访问环形缓冲区,那么必须添加互斥保护机制来确保多个用户互斥访问环形缓冲区。
RingBuf的实现
环形缓冲区
任何收发两端速度不一致的通讯,都需要在它们之间使用一个足够大的FIFO缓冲区。可是多大是大,没有最大,只有更大。
Qt实现环形缓冲区的两种方法
其他参考 http://blog.sina.com.cn/s/blog_b30077de0101gow6.html
变长数据的缓冲区
不管是队列缓冲区还是环形缓冲区,截止现在,没有发现过有类似的实现,都是定长数据单元的。但是在C++中或Qt中,我们可以通过std::queue 或者 QQueue来实现这种需求,如QQueue</QByteArray/>结构,但强迫症让我觉得这很low啊! 在STD和Qt中一直没发现可对外使用的环形缓冲区,QRingBuffer虽然名字是对上了,但是并没有体现出环形缓冲区较对联缓冲区的优势,它是存在内存的开辟和释放的!
Qt信号缓冲能力
Qt系统对signals的最大缓冲能力,超过会怎样?因为之前遇到过,信号堆积的情况,导致槽函数无法执行的情况!怀疑这也是造成Hmi数据处理远大于预估时间的原因之一! 结合在多线程编程分析-HMI假死、定时器碰撞多线程阻塞两个小节的分析,可以基本的认为,如果没有足够处理时间,则信号会堆积但不会丢失,当一有机会,就会一股脑一起执行,这个一股脑的执行相当于一个耗时的操作,其影响的是这个线程内的其他执行过程的机会!
优先级队列
如果菜到不知道优先级队列是专有名词,先看看百科-priority queue
生产与销售
生产与销售的问题中,仓库就是一个缓冲区,能有效的吸收波动,很大程度上减少波动的传递,起到一种解耦作用,由强耦合变成一种松散耦合。