佇列思考二

無痕幽雨發表於2018-02-24

前面一片部落格是佇列的基礎知識: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函式是一個獨立的功能函式,然後像搭積木一樣,搭建一個更大的功能。





相關文章