說說循環緩沖區(Ring Buffer)


關於循環緩沖區(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時,說明緩沖區已滿。

 

 

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

   對此我給出一個簡單的模擬實現Class:

/*
 * =====================================================================================
 *
 *       Filename:  ring_buffer_class.h
 *        Version:  1.0
 *        Created:  2013年11月28日 13時08分04秒
 *       Revision:  none
 *       Compiler:  clang
 *         Author:  sim szm, xianszm007@gmail.com
 *
 * =====================================================================================
 */
#include <iostream>
class ring_buffer {
public:
    ring_buffer( void* buffer, unsigned int buffer_size );
    void buffer_data( const void* data, unsigned int& len );
    void get_Data( void* outData, unsigned int& len );
    void skip_data( unsigned int& len );
    inline unsigned int free_space();
    inline unsigned int buffered_bytes();
private:
    void flush_state();
    unsigned char *read_ptr, *write_ptr;
    unsigned char *end_pos;
    unsigned char *buffer;
    int max_read, max_write, buffer_data_;
};
ring_buffer::ring_buffer( void* buffer, unsigned int buffer_size ) {
    buffer = (unsigned char*)buffer;
    end_pos = buffer + buffer_size;
    read_ptr = write_ptr = buffer;
    max_read = buffer_size;
    max_write = buffer_data_ = 0;
    flush_state();
}
void ring_buffer::buffer_data( const void* data, unsigned int& len ) {
    if ( len > (unsigned int)max_read )
        len = (unsigned int)max_read;
    memcpy( read_ptr, data, len );
    read_ptr += len;
    buffer_data_ += len;
    flush_state();
}
void ring_buffer::get_Data( void* outData, unsigned int& len ) {
    if ( len > (unsigned int)max_write )
        len = (unsigned int)max_write;
    memcpy( outData, write_ptr, len );
    write_ptr += len;
    buffer_data_ -= len;
    flush_state();
}
void ring_buffer::skip_data( unsigned int& len ) {
    unsigned int requestedSkip = len;
    for ( int i=0; i<2; ++i ) {            // 可能會覆蓋,做兩次
        int skip = (int)len;
        if ( skip > max_write )
            skip = max_write;
        write_ptr += skip;
        buffer_data_ -= skip;
        len -= skip;
        flush_state();
    }
    len = requestedSkip - len;
}
inline unsigned int ring_buffer::free_space() {
    return (unsigned int)max_read;
}
inline unsigned int ring_buffer::buffered_bytes() {
    return (unsigned int)buffer_data_;
}
void ring_buffer::flush_state() {
    if (write_ptr == end_pos)	
	  write_ptr = buffer;
    if (read_ptr == end_pos)		
	  read_ptr = buffer;
    if (read_ptr == write_ptr) {
        if ( buffer_data_ > 0 ) {
            max_read = 0;
            max_write = end_pos - write_ptr;
        } else {
            max_read = end_pos - read_ptr;
            max_write = 0;
        }
    } else if ( read_ptr > write_ptr ) {
        max_read = end_pos - read_ptr;
        max_write = read_ptr - write_ptr;
    } else {
        max_read = write_ptr - read_ptr;
        max_write = end_pos - write_ptr;
    }
}

 我們更多要說的是Ring Buffer關於在我們在日志處理方面的一個應用,我們知道對於Program來說日志記錄提供了故障前應用程序狀態的詳細信息,在一段時間的運行過程中,會將不斷地產生大量的跟蹤數據,以及調試信息並持續地將其寫入到磁盤上的文本文件中。多億進行有效的日志記錄,需要使用大量的磁盤空間,並且在多線程環境中,所需的磁盤空間會成倍地增加。常規的日志處理來說存在一些問題,比如硬盤空間的可用性,以及在對一個文件寫入數據時磁盤 I/O 的速度較慢。持續地對磁盤進行寫入操作可能會極大地降低程序的性能,導致其運行速度緩慢。通常,可以通過使用日志輪換策略來解決空間問題,將日志保存在幾個文件中,當這些文件大小達到某個預定義的字節數時,對它們進行截斷和覆蓋。

                                 

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

   當然現在我們面對的大多是多線程的協同工作,對於日志記錄來說,倘若采取傳統的加鎖機制訪問我們的存儲文件,這些線程將在獲得和釋放鎖上花費了大部分的時間,所以采取循環緩沖區會是一個不錯的辦法。通過使得每個線程將數據寫入到它自己的內存塊,就可以完全避免同步問題。當收到來自用戶的轉儲數據的請求時,每個線程獲得一個鎖,並將其轉儲到中心位置。或者分配一個很大的全局內存塊,並將其划分為較小的槽位,其中每個槽位都可由一個線程用來進行日志記錄。每個線程只能夠讀寫它自己的槽位,而不是整個緩沖區。當每個線程第一次嘗試寫入數據時,它會嘗試尋找一個空的內存槽位,並將其標記為忙碌。當線程獲得了一個特定的槽位時,可以將跟蹤槽位使用情況的位圖中相應的位設置為1,當該線程退出時,重新將這個位設置為 0。在這里需要同時需要維護當前使用的槽位編號的全局列表,以及正在使用它的線程的線程信息。

   但是這里需要注意的是當一個線程已經死亡,卻沒有釋放相應的槽位,並在垃圾收集器釋放該槽位之前,再次使用了這個線程 ID 並為其分配一個新的槽位。對於新的線程來說,檢查全局列表並且重用相同的槽位(如果以前的實例使用了它的話),這是非常重要的。因為垃圾收集器線程和寫入者線程可能同時嘗試修改全局列表,所以同樣也需要使用某種鎖定機制。
————————————————
版權聲明:本文為CSDN博主「Shim_ZoMoe」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/sim_szm/article/details/17011545


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM