天天看點

ringbuffer java_循環緩沖區(RingBuffer)

一、簡介

1、循環緩沖區的實作原理

環形緩沖區通常有一個讀指針和一個寫指針。讀指針指向環形緩沖區中可讀的資料,寫指針指向環形緩沖區中可寫的緩沖區。通過移動讀指針和寫指針就可以實作緩沖區的資料讀取和寫入。在通常情況下,環形緩沖區的讀使用者僅僅會影響讀指針,而寫使用者僅僅會影響寫指針。如果僅僅有一個讀使用者和一個寫使用者,那麼不需要添加互斥保護機制就可以保證資料的正确性。如果有多個讀寫使用者通路環形緩沖區,那麼必須添加互斥保護機制來確定多個使用者互斥通路環形緩沖區。

2、概念

關于循環緩沖區(Ring Buffer)的概念,其實來自于Linux核心(Maybe),是為解決某些特殊情況下的競争問題提供了一種免鎖的方法。這種特殊的情況就是當生産者和消費者都隻有一個,而在其它情況下使用它也是必須要加鎖的。對應在Linux核心中有對它的定義:

struct kfifo {

unsigned char *buffer;

unsigned int size;

unsigned int in;

unsigned int out;

spinlock_t *lock;

};

其中buffer指向存放資料的緩沖區,size是緩沖區的大小,in是寫指針下标,out是讀指針下标,lock是加到struct kfifo上的自旋鎖(上面說不加鎖不是這裡的鎖),防止多個程序并發通路此資料結構。當in==out時,說明緩沖區為空;當(in-out)==size時,說明緩沖區已滿。

ringbuffer java_循環緩沖區(RingBuffer)

注:我們保有對應的讀寫指針,當第一批資料(藍色)完成,第二批資料(紅色)會根據目前的寫指針位置繼續我們的資料操作,當達到最大的Buffer_Size時,會重新回到Buffer的開始端。

我們更多要說的是Ring Buffer關于在我們在日志處理方面的一個應用,我們知道對于Program來說日志記錄提供了故障前應用程式狀态的詳細資訊,在一段時間的運作過程中,會将不斷地産生大量的跟蹤資料,以及調試資訊并持續地将其寫入到磁盤上的文本檔案中。多億進行有效的日志記錄,需要使用大量的磁盤空間,并且在多線程環境中,所需的磁盤空間會成倍地增加。正常的日志處理來說存在一些問題,比如硬碟空間的可用性,以及在對一個檔案寫入資料時磁盤 I/O 的速度較慢。持續地對磁盤進行寫入操作可能會極大地降低程式的性能,導緻其運作速度緩慢。通常,可以通過使用日志輪換政策來解決空間問題,将日志儲存在幾個檔案中,當這些檔案大小達到某個預定義的位元組數時,對它們進行截斷和覆寫。

是以要克服空間問題并實作磁盤 I/O 的最小化,某些程式可以将它們的跟蹤資料記錄在記憶體中,僅當請求時才轉儲這些資料。這個循環的、記憶體中的緩沖區稱為循環緩沖區。它可以将相關的資料儲存在記憶體中,而不是每次都将其寫入到磁盤上的檔案中。在需要的時候(比如當使用者請求将記憶體資料轉儲到檔案中時、程式檢測到一個錯誤時,或者由于非法的操作或者接收到的信号而引起程式崩潰時)可以将記憶體中的資料轉儲到磁盤。循環緩沖區日志記錄由一個固定大小的記憶體緩沖區構成,程序使用這個記憶體緩沖區進行日志記錄。

當然現在我們面對的大多是多線程的協同工作,對于日志記錄來說,倘若采取傳統的加鎖機制通路我們的存儲檔案,這些線程将在獲得和釋放鎖上花費了大部分的時間,是以采取循環緩沖區會是一個不錯的辦法。通過使得每個線程将資料寫入到它自己的記憶體塊,就可以完全避免同步問題。當收到來自使用者的轉儲資料的請求時,每個線程獲得一個鎖,并将其轉儲到中心位置。或者配置設定一個很大的全局記憶體塊,并将其劃分為較小的槽位,其中每個槽位都可由一個線程用來進行日志記錄。每個線程隻能夠讀寫它自己的槽位,而不是整個緩沖區。當每個線程第一次嘗試寫入資料時,它會嘗試尋找一個空的記憶體槽位,并将其标記為忙碌。當線程獲得了一個特定的槽位時,可以将跟蹤槽位使用情況的位圖中相應的位設定為1,當該線程退出時,重新将這個位設定為 0。在這裡需要同時需要維護目前使用的槽位編号的全局清單,以及正在使用它的線程的線程資訊。

但是這裡需要注意的是當一個線程已經死亡,卻沒有釋放相應的槽位,并在垃圾收集器釋放該槽位之前,再次使用了這個線程 ID 并為其配置設定一個新的槽位。對于新的線程來說,檢查全局清單并且重用相同的槽位(如果以前的執行個體使用了它的話),這是非常重要的。因為垃圾收集器線程和寫入者線程可能同時嘗試修改全局清單,是以同樣也需要使用某種鎖定機制。

二、Ring Buffer的優點

我們使用 Ring Buffer 這種資料結構,是因為它給我們提供了可靠的消息傳遞特性。這個理由就足夠了,不過它還有一些其他的優點。

首先,Ring Buffer 比連結清單要快,因為它是數組,而且有一個容易預測的通路模式。這很不錯,對 CPU 高速緩存友好 (CPU-cache-friendly)-資料可以在硬體層面預加載到高速緩存,是以 CPU 不需要經常回到主記憶體 RAM 裡去尋找 Ring Buffer 的下一條資料。

第二點,Ring Buffer 是一個數組,你可以預先配置設定記憶體,并保持數組元素永遠有效。這意味着記憶體垃圾收集(GC)在這種情況下幾乎什麼也不用做。此外,也不像連結清單那樣每增加一條資料都要建立對象-當這些資料從連結清單裡删除時,這些對象都要被清理掉。

參考網址:

圖檔說明: