【轉載】無鎖環形佇列的一種高效實現
1.環形佇列是什麼
佇列是一種常用的資料結構,這種結構保證了資料是按照“先進先出”的原則進行操作的,即最先進去的元素也是最先出來的元素.環形佇列是一種特殊的佇列結構,保證了元素也是先進先出的,但與一般佇列的區別是,他們是環形的,即佇列頭部的上個元素是佇列尾部,通常是容納元素數固定的一個閉環。
2.環形佇列的優點
1.保證元素是先進先出的
是由佇列的性質保證的,在環形佇列中通過對佇列的順序訪問保證。
2.元素空間可以重複利用
因為一般的環形佇列都是一個元素數固定的一個閉環,可以在環形佇列初始化的時候分配好確定的記憶體空間,當進隊或出隊時只需要返回指定元素記憶體空間的地址即可,這些記憶體空間可以重複利用,避免頻繁記憶體分配和釋放的開銷。
3.為多執行緒資料通訊提供了一種高效的機制。
在最典型的生產者消費者模型中,如果引入環形佇列,那麼生成者只需要生成“東西”然後放到環形佇列中即可,而消費者只需要從環形佇列裡取“東西”並且消費即可,沒有任何鎖或者等待,巧妙的高效實現了多執行緒資料通訊。
3.環形佇列的工作場景
一般應用於需要高效且頻繁進行多執行緒通訊傳遞資料的場景,例如:linux捕包、發包等等,(linux系統中對PACKET_RX_RING和PACKET_TX_RING的支援實質就是核心實現的一種環形佇列)
實際環形佇列在工作時有3種情況:
3.1 入隊速度=出隊速度
這是環形佇列的常態,即入隊速度和出隊速度大致一樣,即使某個突然時刻入隊速度陡然變高或者出隊速度陡然變低,都能通過佇列這個緩衝區把這些資料先存起來,等到能處理的時候再處理。
3.2 入隊速度>出隊速度
在這種情況下,佇列“寫入”的速度>“讀取”的速度,想象當這種狀態持續一段時間之後,佇列中大多數全是寫入但沒讀取的元素,當又一個新的元素產生時,可以把這個新元素drop掉或者放在另一個緩衝區儲存起來,這種情況的出現不是個好事情,說明你需要對出隊處理元素的演算法或邏輯優化處理速度了。
3.3 入隊速度<出隊速度
在這種情況下,佇列“讀取”速度>“寫入”速度,這種情況說明程式出隊處理元素的速度很快,這是比較好的情況,唯一不足的是讀取佇列的時候可能經常會輪詢佇列是否有新的元素,造成cpu佔用過高。
4.無鎖環形佇列的實現
4.1環形佇列的儲存結構
連結串列和線性表都是可以的,但幾乎都用線性表實現,比連結串列快很多,原因也是顯而易見的,因為訪問連結串列需要挨個遍歷。
4.2讀寫index
有2個index很重要,一個是寫入index,標示了當前可以寫入元素的index,入隊時使用。一個是讀取index,標示了當前可以讀取元素的index,出隊時使用。
4.3元素狀態切換
有種很巧妙的方法,就是在佇列中每個元素的頭部加一個元素標示欄位,標示這個元素是可讀還是可寫,而這個的關鍵就在於何時設定元素的可讀可寫狀態,參照linux核心實現原理,當這個元素讀取完之後,要設定可寫狀態,當這個元素寫入完成之後,要設定可讀狀態。
5.實際測試效果
在CentOS 5.5 (cpu每核主頻1200MHz)裝置上簡單測試了一下。環形佇列大小為10000,元素資料大小為4位元組,每秒可以寫入並讀取的元素是250萬左右,即250pps.