首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

環形隊列深度解析:定長數組實現 O(1) 入隊出隊、偽滿判斷、無鎖併發與工程級封裝

     編輯:關於C語言

前言

環形隊列 (Circular/Ring Buffer) 是有界、FIFO、O(1) 入隊出隊、零內存碎片、極致緩存友好**的數據結構王者。本文從數組佈局與頭尾指針語義、偽滿/偽空判斷策略、動態擴容權衡、無鎖單生產單消費 (SPSC) 實現、C++ RAII 封裝、測試驗證矩陣**提供完整工程指南。

一、核心數據結構與指針語義


typedef struct {
    int *buf;        // 數據緩衝區
    size_t cap;      // 容量 (必須是 2 的冪次以利用位運算優化,或任意值用取模)
    size_t head;     // 隊頭索引 (下一個出隊位置)
    size_t tail;     // 隊尾索引 (下一個入隊位置)
    // 可選:原子變量用於無鎖版
} RingQueue;

不變量:

  • 空:head == tail
  • 滿:(tail + 1) % cap == head (犧牲 1 個槽位區分滿/空) — 經典偽滿策略
  • 元素數:(tail - head + cap) % cap

二、基礎操作實現 (單線程版)


RingQueue* rq_create(size_t capacity) {
    RingQueue *q = malloc(sizeof(RingQueue));
    if (!q) return NULL;
    q->buf = malloc(capacity * sizeof(int));
    if (!q->buf) { free(q); return NULL; }
    q->cap = capacity;
    q->head = q->tail = 0;
    return q;
}

void rq_destroy(RingQueue *q) {
    if (q) { free(q->buf); free(q); }
}

// 入隊:返回 0 成功,-1 滿
int rq_push(RingQueue *q, int val) {
    if (!q) return -1;
    size_t next_tail = (q->tail + 1) % q->cap;
    if (next_tail == q->head) return -1; // 滿
    q->buf[q->tail] = val;
    q->tail = next_tail;
    return 0;
}

// 出隊:返回 0 成功,-1 空
int rq_pop(RingQueue *q, int *out_val) {
    if (!q || !out_val) return -1;
    if (q->head == q->tail) return -1; // 空
    *out_val = q->buf[q->head];
    q->head = (q->head + 1) % q->cap;
    return 0;
}

size_t rq_size(const RingQueue *q) {
    return (q->tail - q->head + q->cap) % q->cap;
}

size_t rq_capacity(const RingQueue *q) {
    return q->cap;
}

三、三種滿/空判斷策略對比

策略空條件滿條件可用容量優缺點
犧牲一槽位 (經典)head == tail(tail+1)%cap == headcap - 1最簡單,無額外變量,浪費 1 槽
額外 size 計數器size == 0size == capcap100% 利用率,需原子操作 size 變量 (併發開銷)
標誌位 flaghead==tail && !flaghead==tail && flagcap無 size 開銷,但 flag 讀寫需原子/鎖

推薦:單線程/鎖保護用 size 計數器 (代碼清晰、100% 利用率);無鎖 SPSC 用 犧牲一槽位 (無共享變量、純指針 CAS-free)。

四、無鎖 SPSC 實現 (C11 _Atomic / C++ std::atomic)

單生產者單消費者場景,head 僅消費者寫、生產者讀;tail 僅生產者寫、消費者讀。**無需 CAS**,僅需內存序保證可見性。


#include 

typedef struct {
    int *buf;
    size_t cap;
    _Atomic size_t head; // 消費者寫
    _Atomic size_t tail; // 生產者寫
} LockFreeRingQueue;

// 生產者
int lfrq_push(LockFreeRingQueue *q, int val) {
    size_t tail = atomic_load_explicit(&q->tail, memory_order_relaxed);
    size_t next_tail = (tail + 1) % q->cap;
    // 載入 head (acquire 序確保看到消費者最新 head)
    size_t head = atomic_load_explicit(&q->head, memory_order_acquire);
    if (next_tail == head) return -1; // 滿
    
    q->buf[tail] = val; // 寫數據
    // 發布 tail (release 序確保數據寫入對消費者可見)
    atomic_store_explicit(&q->tail, next_tail, memory_order_release);
    return 0;
}

// 消費者
int lfrq_pop(LockFreeRingQueue *q, int *out_val) {
    size_t head = atomic_load_explicit(&q->head, memory_order_relaxed);
    // 載入 tail (acquire 序確保看到生產者最新 tail)
    size_t tail = atomic_load_explicit(&q->tail, memory_order_acquire);
    if (head == tail) return -1; // 空
    
    *out_val = q->buf[head]; // 讀數據
    size_t next_head = (head + 1) % q->cap;
    // 發布 head (release 序)
    atomic_store_explicit(&q->head, next_head, memory_order_release);
    return 0;
}

關鍵點:memory_order_acquire/release 構成 Release-Acquire 順序一致性,保證生產者寫入數據 -> 發布 tail -> 消費者讀取 tail -> 讀取數據 的有序性。relaxed 僅用於本線程讀取自身寫入的索引。

五、C++ RAII 封裝與 STL 風格接口


template 
class RingBuffer {
    T data_[N];
    size_t head_ = 0, tail_ = 0, size_ = 0;
public:
    bool push(const T& v) {
        if (size_ == N) return false;
        data_[tail_] = v;
        tail_ = (tail_ + 1) % N;
        ++size_;
        return true;
    }
    bool pop(T& out) {
        if (size_ == 0) return false;
        out = data_[head_];
        head_ = (head_ + 1) % N;
        --size_;
        return true;
    }
    bool front(T& out) const { if (size_==0) return false; out=data_[head_]; return true; }
    size_t size() const { return size_; }
    size_t capacity() const { return N; }
    bool empty() const { return size_ == 0; }
    bool full() const { return size_ == N; }
    void clear() { head_=tail_=size_=0; }
};

六、動態擴容:權衡與實現

環形隊列通常不擴容** (破壞 O(1) 保證、指針失效、內存抖動)。若必須:

  1. 分配 2x 新緩衝區。
  2. 按邏輯順序拷貝舊數據 (兩段 memcpy)。
  3. 原子交換指針 (需鎖或 RCU)。
  4. 釋放舊緩衝區 (延遲釋放防止使用後釋放)。

替代方案:預分配足夠大容量、或用 std::deque/boost::circular_buffer (支持擴容)。

七、測試驗證矩陣

測試描述驗證點
基本 FIFOPush 1..N, Pop 驗證順序邏輯正確性
環繞Push 滿, Pop 一半, Push 填滿, Pop 全部索引環繞、偽滿判斷
邊界空 Pop、滿 Push、容量 1返回碼、不崩潰
併發 SPSC生產者線程 Push 100萬, 消費者 Pop 驗證無丟失/重複/順序無鎖正確性、內存序
併發 MPMC多生產多消費 (加鎖版),壓力測試鎖競爭、死鎖自由
性能基準單線程 Push/Pop 1億次 耗時、對比 std::queue常數因子、緩存命中
內存序驗證編譯器屏障測試、TSAN/Helgrind 檢測無數據競爭

八、工程落地清單

  1. 容量取 2 的冪次:cap & (cap - 1) == 0,用 & (cap - 1) 替代 % cap,消除除法指令。
  2. 緩存行填充:head 和 tail 分離至不同緩存行 (64 Bytes),防止偽共享 導致多核性能崩塌。
  3. 類型泛化:用 void* + 元素大小 或 C++ 模板支持任意類型 (需構造/析構)。
  4. 批量操作:push_n/pop_n 減少函數調用/原子操作開銷,利用 memcpy 向量化。
  5. 監控埋點:高水位標記、擠壓計數、延遲直方圖。

結語

環形隊列以「固定內存、頭尾指針追趕、模運算封閉」的極簡幾何邏輯,實現了理論最優的有界 FIFO 語義。掌握偽滿策略選擇、無鎖 SPSC 內存序模型、緩存行對齊防偽共享、批量向量化**四大工程精髓,即可在高性能網絡框架、實時音視頻管線、Lock-Free 數據交換、嵌入式中斷緩衝等核心鏈路中構建零拷貝、零鎖、零分配的高吞吐通道。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved