前面一片博客是队列的基础知识:http://blog.csdn.net/wuhenyouyuyouyu/article/details/53939109
这篇博客,就是对于队列的应用说出自己的想法,有不对的地方,希望大家批评指正。
队列其实就是一个先进先出的FIFO,提供了一组维护这个FIFO的服务集合。至于队列里面
放了什么东西,它并不关心。那么问题来啦?我们怎么去用这些队列提供的服务呢?有些
人可能会笑了,这还不简单,最典型的生产者-消费者模型,生产者不停的往队列里面放
东西,消费者不停的往外拿东西。这个是一对一的情况,如果是一个生产者对应多个消费者,
1对N的情况呢?这个时候,我们又该怎么处理?到了这里,我们是否应该思考下,如何去保
证队列的数据正确的分发给对应的消费者,每个消费者怎么做好对于全局资源(队列)的同步
访问。
我个人理解,为了解决上述问题,可以结合运用客户端-服务器模式和中介者模式。
首先来看看客户端/服务器模式,是站在什么角度看待问题:
每个消费者都是客户端,而队列是服务器,提供一些服务。客户端要做的事件就是根据
服务器提供的服务来搜索自己的数据(例如peek服务),并且反馈给服务器搜索结果(没有找到
自己的数据-丢弃,找到自己的数据-出队,信息不足-保留等等),服务器就是根据客户端的请
求来维护自己的数据,而数据的内容并不关心,甚至是不知道。
其次来看看中介者模式,是站在什么角度看待问题:
这里队列里的数据是公共资源,每个消费者都可以访问,那么就存在资源同步问题,如
果资源的同步让每个消费者去维护,并且分发消息,那么就要求每个消费者都可以通信,也就
是说每个消费者都是知道彼此的,这会导致逻辑的急剧复杂甚至有时候是不可能的,比如:某
两个客户端不能相互通信。那么这个时候把服务器看做中介者,由它去维护就简单的多了。
这里用队列去做了类比,其实不仅仅是队列,我们可以类比为我们提供了某种服务,并且
提供了维护服务,那么作为被服务方,只需要根据我提供的服务去做自己的事情,并且反馈给
我结果,由我来维护,至于服务里面的具体被包装的信息,我并不关心。用港口去做类比,我
感觉很恰到,作为港口的管理人员,不需要关心货物是什么,只需要关心货物到来-暂存,然后
交给对应的人员就可以。当然了,这里不涉及安全,如果要考虑安全,还需要我们自己去过滤,
把一些危险物品剔除掉。
2018.03.20 16:43
上面的文档提出了一个高度抽象的“协议解析引擎”,其作用就是有个协议解析core。那么用户
接口怎么用设计呢?
用不用把queue注册进来?为什么注册进来呢,是为了给core里面的对于队列的API使用。那么
问题来了,假如我操作的数据不是queue,是其它类型的数据;还有就是我们思考下,这个queue有
没有必要注册进去?我们思考下这个queue对于我的core有没有作用?答案是没有作用,因为它是
传给操作queue的API接口用的,我们的core依赖于具体的接口,对于struct queue没有要求。想到这点,
我们是否可以这么认为,我的core只需要用户提供的API接口即可,具体这个接口是怎么操作data struct
的,我们不关心。
到这里有人可能会说,需要四个API接口,这个需要费4个指针,为什么不extern API呢?我们可以定义
一个API接口的struct,然后初始化为const,最后把这个const struct的指针注册进来,这样就是一个指
针RAM。
2018.05.09 10.01
上节说道,把对于queue的操作API打包为一个const struct数据结构体,然后注册进来。对于所有用户服务
都是同一个queue的,即所有的用户服务都是使用同一套API,那么我可以把使用如下形式:
API接口:
服务链表:
核心服务:
核心服务里面有一个API接口合集,提供给所有的服务使用。那么我们思考如下,如果每个用户服务需要的
API都是不同的,怎么办?修改服务链表数据结构,增加一个API接口合集,然后修改注册服务API接口,增加
queue的API结构体变量,这样当协议解析引擎调用用户服务时候,就可以提供相应的API服务集合。
2018.05.24 18:20
最近一直在做多协议解析,要加入一个xmodem协议模块,因为RAM只有4K,因此我想把UART的接收队列
缓存开辟小一点(1K-xmodem数据长度=1k),但是编程中发现,这么做是不行的,队列缓存长度必须>=
最大协议包长,否则多协议解析就有问题。虽然是个很明显的结论,但是我确实刚刚想明白。哎,记录下吧。。。。。。
2018.06.14 09:31
对queue也使用了一段时间,对于它的的API接口设计做下总结吧,为什么设计这个接口,有什么作用,应用场景是什么。
bool init_byte_queue( byte_queue_t* ptQueue,uint8_t* pchBuffer,uint16_t hwSize)
这个接口必须保留,用于创建queue对象,同时做些初始化工作。
bool is_queue_empty( byte_queue_t* ptQueue)
判断队列是否为空,我感觉意义不大,因为出队有反馈操作是否成功。
bool enqueue_byte( byte_queue_t* ptQueue,uint8_t chInData)
入队一个字节操作,这个适用于想UART,SPI等基于单字节流的操作,如果是多字节流,应该增加一个enqueue_bytes(称为块入)。应用场景,对于协议组包发送,一个协议帧可能包含多个不同属性的不同数据项,按照常规操作,我们一般都是用绝对偏移地址操作,这个效率更高;但是我们考虑后期的维护升级,增加一些数据项或者减少一些数据项,需要把后面的所有数据偏移地址重新计算,你想想是不是就很头大。。。。。。
bool dequeue_byte( byte_queue_t* ptQueue,uint8_t* pchOutData)
出队一个字节操作,这个适合于单一的出口,或者是单一协议解析。如果入队具有突发性,为了提高出队效率,可以增加一个
dequeue_bytes和dequeue_all_bytes接口(称为块出)
bool apply_semaphore_queue( byte_queue_t* ptQueue)
bool release_semaphore_queue( byte_queue_t* ptQueue)
对于queue资源的申请和释放(资源锁,或者叫互斥锁),适合于多入的情景。举个例子,UART的发送队列,APP层需要把
发送数据拷贝到发送队列,但是发送数据长度大于发送队列,这个时候我们只能一部分一部分拷贝,假如我们拷贝了一分部数据,在等待的同时有其它任务也需要发送数据,如果没有互斥锁,数据流是乱的。对于单一入口场景,不需要考虑。
bool peek_byte_queue( byte_queue_t* ptQueue,uint8_t* pchPeekData);
bool get_all_peeked_byte_queue( byte_queue_t* ptQueue);
bool reset_peek_byte_queue( byte_queue_t* ptQueue);
这三个接口又有什么用呢?它们是为了配合多出口场景(多协议解析)使用,考虑下面的应用场景:UART的接收缓存里面存有多种格式的协议,每个协议对应于一个业务块,这个时候peek技术就派上用场了;对于单协议解析,意义不大。
2018.07.03 14:15
软件的模块化分为纵向和横向两个方面,横向就是相同类型不同功能的模块,像UART驱动,SPI驱动等;纵向是不同功能或者业务逻辑的模块,如:底层驱动层,OS层,上层业务逻辑层。再往小了看,同一个模块内的分层,横向看,就是提供给用户的不同的API接口;纵向看,就是提供给用户的API实现,调用了一些内部的static function,这些static函数是一个独立的功能函数,然后像搭积木一样,搭建一个更大的功能。