生產者消費者模式下的可覆蓋環形資料緩衝結構
1、簡介
生產者—消費者模式是軟體架構中最常用到的模式,在實際的軟體開發過程中,經常會碰到如下場景:某個模組負責產生資料,這些資料由另乙個模組來負責處理(此處的模組是廣義的,可以是類、函式、執行緒、程序等)。產生資料的模組,就形象地稱為生產者;而處理資料的模組,就稱為消費者。
單單抽象出生產者和消費者,還夠不上是生產者/消費者模式。該模式還需要有乙個緩衝區處於生產者和消費者之間,作為乙個中介。生產者把資料放入緩衝區,而消費者從緩衝區取出資料。大概的結構如下圖。
生產者不停的將資料放入緩衝區,即是物理記憶體,如果生產者產生資料的速度過快以至於消費者來不及將資料讀出,這個緩衝區就會產生溢位的現象,解決這個問題最好的辦法就是採用環形緩衝區。先在物理記憶體開闢一塊單獨的空間,採用鍊錶的形式將這塊記憶體連線成環狀,產生的資料和將要被讀取的資料都在這個環狀的記憶體緩衝區中,於是不會出現記憶體溢位的問題。
但是這種緩衝結構也有乙個缺點,當寫入資料的速度遠遠大於讀取的速度時,寫入位已經經過一圈到達讀出位前乙個資料單元,這時通常的做法是讓寫入等待,這有讀取完乙個單元,才能空出這個單元用以寫入,實際生產過程中對於一些資料是可以捨棄掉的,同時,捨棄掉的必須是最老也就是最早寫入的資料,而必須將最新的資料儲存下來,按照傳統做法是不能實現這種要求的,這也是本文可覆蓋環形緩衝區的由來。
2、概述
思考下面一種場景,軟體設計中有兩個執行緒,執行緒1用來接收網絡卡中的資料報,並將這些資料報解析後寫入同樣結構的結構體中,而另外乙個執行緒2則需要將結構體中的資料寫入資料庫。這是乙個非常典型的生產者消費者模式,我們需要在這兩個執行緒中間架設乙個環形緩衝區,執行緒1將網絡卡資料寫入緩衝區,執行緒2在這個緩衝區中讀取資料,這樣兩個執行緒不直接依賴,耦合程度降低,易於進行處理。
在這個軟體結構中,理想的情況下,寫入緩衝區的速度與讀緩衝區的速度保持著某種均衡,執行緒2總是可以將緩衝區的資料讀完,而執行緒1不會領先執行緒2一圈以至於執行緒1只有等待,大概結構如下圖所示:
看著這個結構可以想象得到,此時寫執行緒和讀執行緒保持一定距離,如果距離保持這樣,那麼讀執行緒永遠追不上寫執行緒,這是乙個穩定的讀寫過程。但是寫執行緒的速度如果大於讀的速度,例如讀執行緒在讀取3號節點的資料,而寫執行緒已經經過一圈寫到2號節點,當寫完2號節點時,如果按照傳統流程,寫執行緒將處於等待狀態,但是由於我們更加關注網絡卡最新的資料,如果這時寫執行緒等待,那麼大量的最新資料將會被丟棄掉,這不是我們想看到的,於是我們選擇丟棄掉老的資料,用新資料來對老節點進行覆蓋,那麼這時寫執行緒將從4號節點開始寫入,依次向下。
當有老的未讀的資料被新的資料覆蓋的時候,例如,寫執行緒這時已經覆蓋掉4號和5號節點的資料,而這時讀執行緒剛好讀完3號節點的資料,由於4、5號節點的資料都是最新的,按照邏輯,這兩個節點的資料應該最後讀取,所以這時讀執行緒應該讀取6號節點的資料,依次向下。
以上就是可覆蓋緩衝區的邏輯結構,下一節描述整個過程的具體實現。
3、實現
3.1 寫部分
寫執行緒從網絡卡拿到資料後,經過處理分析,儲存到環形快取的結構體中,當對乙個節點進行寫操作時,鎖住這個節點,這樣,讀執行緒會繞過這個節點讀取下乙個。
while(nethead->next->lock) //每寫完乙個節點,解鎖這個節點,同時將這個節點的位址放入乙個任務佇列中,如果寫入佇列的節點位址和佇列頭部的節點位址相同,則將頭部位址彈出寫入隊尾。nethead為將要寫入資料的節點
nethead=nethead->next;
nethead=nethead->next;
nethead->lock=true;
if(t==m_taskqueue.front()) //3.2 讀部分m_taskqueue是將要讀取資料的任務佇列
else
讀執行緒從任務佇列中彈出頭節點的位址,再在這個位址的節點中取出想要的資料,讀取這個節點的時候從鎖住這個節點,同時在任務佇列中彈出頭結點。
while(!m_taskqueue.empty())3.3 總結}
這個結構最關鍵的地方在於,寫完節點後放入任務佇列中,這裡增加了乙個判斷,即如果寫入佇列的節點位址和佇列頭部的節點位址相同,則將頭部位址彈出寫入隊尾。
環形資料緩衝的實現
關鍵在於寫指標不能追上讀指標,讓讀指標和寫指標保留乙個位元組的距離來區分2個指標重疊的情況。如何保證執行緒安全的?其實就是某個執行緒在讀取或者寫入的時候取的某個時間點的指標來判斷時候符合條件,條件只能是越來越好,而不可能是越來越差。比如判斷能夠寫入的時候,空間肯定是越來越多的 實際的緩衝區大小要多乙...
環形陣列例程
乙個簡單的環形陣列例程,參考了網路上的資源。define circlebuffer size 8 unsigned char circlebuffer circlebuffer size unsigned char writeindex 0 unsigned char readindex 0 uns...
環形緩衝區 環形緩衝佇列學習
專案中需要執行緒之間共享乙個緩衝fifo佇列,乙個執行緒往佇列中添資料,另乙個執行緒取資料 經典的生產者 消費者問題 開始考慮用stl的vector容器,但不需要隨機訪問,頻繁的刪除最前的元素引起記憶體移動,降低了效率。使用linklist做佇列的話,也需要頻繁分配和釋放結點記憶體。於是自己實現乙個...