當有大量數據的時候,我們不能存儲所有的數據,那么計算機處理數據的時候,只能先處理先來的,那么處理完后呢,就會把數據釋放掉,再處理下一個。那么,已經處理的數據的內存就會被浪費掉。因為后來的數據只能往后排隊,如過要將剩余的數據都往前移動一次,那么效率就會低下了,肯定不現實,所以,環形隊列就出現了。
1、環形隊列:
目的:避免頻繁的內存創建取消、分配。內存一直只用了一塊。
在發送線程使用的是普通隊列。在發送任務處理的事件循環線程用的是環形隊列。即 一個環形緩沖區.
消息隊列解決了 鎖調用太頻繁的問題,另一個讓人有些苦惱的大概是這太多的內存分配和釋放操作了。頻繁的內存分配不但增加了系統開銷,更使得內存碎片不斷增多,非常不利於我們的服務器長期穩定運行。也許我們可以使用內存池,比如SGI STL中附帶的小內存分配器。但是對於這種按照嚴格的先進先出順序處理的,塊大小並不算小的,而且塊大小也並不統一的內存分配情況來說,更多使用的是一種叫做環形緩沖區的方案,按照嚴格的先進先出順序進行處理,這是環形緩沖區的使用必須遵守的一項要求。
環形緩沖區是一項很好的技術,不用頻繁的分配內存,而且在大多數情況下,內存的反復使用也使得我們能用更少的內存塊做更多的事。
在網絡IO線程中,我們會為每一個連接都准備一個環形緩沖區,用於臨時存放接收到的數據,以應付半包及粘包的情況。在解包及解密完成后,我們會將這個數據包復制到邏輯線程消息隊列中,如果我們只使用一個隊列,那這里也將會是個環形緩沖區,IO線程往里寫,邏輯線程在后面讀,互相追逐。
在通信程序中,經常使用環形緩沖區作為數據結構來存放通信中發送和接收的數據。環形緩沖區是一個先進先出的循環緩沖區,可以向通信程序提供對緩沖區的互斥訪問。
2、環形緩沖區的實現原理
環形緩沖區通常有一個讀指針和一個寫指針。讀指針指向環形緩沖區中可讀的數據,寫指針指向環形緩沖區中可寫的緩沖區。通過移動讀指針和寫指針就可以實現緩沖區的數據讀取和寫入。在通常情況下,環形緩沖區的讀用戶僅僅會影響讀指針,而寫用戶僅僅會影響寫指針。如果僅僅有一個讀用戶和一個寫用戶,那么不需要添加互斥保護機制就可以保證數據的正確性。如果有多個讀寫用戶訪問環形緩沖區,那么必須添加互斥保護機制來確保多個用戶互斥訪問環形緩沖區。
環形緩沖區所有的push和pop操作都是在一個固定 的存儲空間內進行。而隊列緩沖區在push的時候,可能會分配存儲空間用於存儲新元素;在pop時,可能會釋放廢棄元素的存儲空間。所以環形方式相比隊列方式,少掉了對於緩沖區元素所用存儲空間的分配、釋放。這是環形緩沖區的一個主要優勢。
使用鏈表的方式,正好和數組相反。鏈表省去了頭尾相連的特殊處理。但是鏈表在初始化的時候比較繁瑣,而且在有些場合(比如后面提到的跨進程的IPC)不太方便使用。
-
讀寫操作
環形緩沖區要維護兩個索引,分別對應寫入端(W)和讀取端(R)。寫入(push)的時候,先確保環沒滿,然后把數據復制到W所對應的元素,最后W指向下一個元素;讀取(pop)的時候,先確保環沒空,然后返回R對應的元素,最后R指向下一個元素。
-
判斷“空”和“滿”
上述的操作並不復雜,不過有一個小小的麻煩:空環和滿環的時候,R和W都指向同一個位置!這樣就無法判斷到底是“空”還是“滿”。大體上有兩種方法可以解決該問題。
辦法1:始終保持一個元素不用
當空環的時候,R和W重疊。當W比R跑得快,追到距離R還有一個元素間隔的時候,就認為環已經滿。當環內元素占用的存儲空間較大的時候,這種辦法顯得很土(浪費空間)。
辦法2:維護額外變量
如果不喜歡上述辦法,還可以采用額外的變量來解決。比如可以用一個整數記錄當前環中已經保存的元素個數(該整數>=0)。當R和W重疊的時候,通過該變量就可以知道是“空”還是“滿”。
-
元素的存儲
由於環形緩沖區本身就是要降低存儲空間分配的開銷,因此緩沖區中元素的類型要選好。盡量存儲值 類型的數據,而不要存儲指針(引用) 類型的數據。因為指針類型的數據又會引起存儲空間(比如堆內存)的分配和釋放,使得環形緩沖區的效果打折扣。