C++环形缓冲区实践与注意事项

C++环形缓冲区实践与注意事项

环形缓冲区(Circular Buffer)是一种高效的数据结构,特别适用于生产者-消费者场景、数据流处理和缓存管理。下面我将详细介绍环形缓冲区的实现原理、代码实践和注意事项。

环形缓冲区核心概念

环形缓冲区通过固定大小的数组和两个指针(读指针和写指针)实现循环使用存储空间。当指针到达数组末尾时,会回到数组开头继续操作。

完整实现代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <chrono>
#include <atomic>
#include <stdexcept>

template<typename T>
class CircularBuffer {
public:
    // 构造函数
    explicit CircularBuffer(size_t size) 
        : buffer_(size), capacity_(size), read_pos_(0), write_pos_(0), 
          count_(0), is_full_(false) {
        if (size == 0) {
            throw std::invalid_argument("Buffer size must be greater than 0");
        }
    }

    // 默认析构函数
    ~CircularBuffer() = default;

    // 禁止拷贝和赋值
    CircularBuffer(const CircularBuffer&) = delete;
    CircularBuffer& operator=(const CircularBuffer&) = delete;

    // 写入数据(非阻塞)
    bool push(const T& item) {
        std::lock_guard<std::mutex> lock(mutex_);
        
        if (is_full_) {
            return false; // 缓冲区已满
        }

        buffer_[write_pos_] = item;
        write_pos_ = (write_pos_ + 1) % capacity_;
        
        ++count_;
        is_full_ = (write_pos_ == read_pos_);
        
        not_empty_.notify_one();
        return true;
    }

    // 写入数据(阻塞)
    bool push_blocking(const T& item, std::chrono::milliseconds timeout = std::chrono::milliseconds(0)) {
        std::unique_lock<std::mutex> lock(mutex_);
        
        if (timeout.count() > 0) {
            // 带超时的等待
            if (!not_full_.wait_for(lock, timeout, [this]() { return !is_full_; })) {
                return false; // 超时
            }
        } else {
            // 无限等待
            not_full_.wait(lock, [this]() { return !is_full_; });
        }

        buffer_[write_pos_] = item;
        write_pos_ = (write_pos_ + 1) % capacity_;
        
        ++count_;
        is_full_ = (write_pos_ == read_pos_);
        
        not_empty_.notify_one();
        return true;
    }

    // 读取数据(非阻塞)
    bool pop(T& item) {
        std::lock_guard<std::mutex> lock(mutex_);
        
        if (empty()) {
            return false; // 缓冲区为空
        }

        item = buffer_[read_pos_];
        read_pos_ = (read_pos_ + 1) % capacity_;
        
        --count_;
        is_full_ = false;
        
        not_full_.notify_one();
        return true;
    }

    // 读取数据(阻塞)
    bool pop_blocking(T& item, std::chrono::milliseconds timeout = std::chrono::milliseconds(0)) {
        std::unique_lock<std::mutex> lock(mutex_);
        
        if (timeout.count() > 0) {
            // 带超时的等待
            if (!not_empty_.wait_for(lock, timeout, [this]() { return !empty(); })) {
                return false; // 超时
            }
        } else {
            // 无限等待
            not_empty_.wait(lock, [this]() { return !empty(); });
        }

        item = buffer_[read_pos_];
        read_pos_ = (read_pos_ + 1) % capacity_;
        
        --count_;
        is_full_ = false;
        
        not_full_.notify_one();
        return true;
    }

    // 查看但不移除数据
    bool peek(T& item) const {
        std::lock_guard<std::mutex> lock(mutex_);
        
        if (empty()) {
            return false;
        }

        item = buffer_[read_pos_];
        return true;
    }

    // 清空缓冲区
    void clear() {
        std::lock_guard<std::mutex> lock(mutex_);
        read_pos_ = write_pos_;
        count_ = 0;
        is_full_ = false;
        // 注意:这里不需要通知条件变量,因为清空操作不会让缓冲区变满
    }

    // 判断是否为空
    bool empty() const {
        // 注意:当 read_pos_ == write_pos_ 且 !is_full_ 时为空
        return (!is_full_ && (read_pos_ == write_pos_));
    }

    // 判断是否已满
    bool full() const {
        return is_full_;
    }

    // 获取当前元素数量
    size_t size() const {
        return count_;
    }

    // 获取容量
    size_t capacity() const {
        return capacity_;
    }

    // 获取剩余空间
    size_t available() const {
        return capacity_ - count_;
    }

private:
    std::vector<T> buffer_;
    size_t capacity_;
    mutable std::mutex mutex_;
    std::condition_variable not_empty_;
    std::condition_variable not_full_;
    
    size_t read_pos_;   // 读位置
    size_t write_pos_;  // 写位置
    size_t count_;      // 当前元素数量
    bool is_full_;      // 是否已满标志
};

// 无锁环形缓冲区(适用于单生产者单消费者场景)
template<typename T, size_t Size>
class LockFreeCircularBuffer {
public:
    LockFreeCircularBuffer() : read_index_(0), write_index_(0) {}

    bool push(const T& item) {
        size_t current_write = write_index_.load(std::memory_order_relaxed);
        size_t next_write = next_index(current_write);
        
        // 检查是否已满
        if (next_write == read_index_.load(std::memory_order_acquire)) {
            return false;
        }
        
        buffer_[current_write] = item;
        write_index_.store(next_write, std::memory_order_release);
        return true;
    }

    bool pop(T& item) {
        size_t current_read = read_index_.load(std::memory_order_relaxed);
        
        // 检查是否为空
        if (current_read == write_index_.load(std::memory_order_acquire)) {
            return false;
        }
        
        item = buffer_[current_read];
        read_index_.store(next_index(current_read), std::memory_order_release);
        return true;
    }

    bool empty() const {
        return read_index_.load(std::memory_order_acquire) == 
               write_index_.load(std::memory_order_acquire);
    }

    bool full() const {
        size_t next_write = next_index(write_index_.load(std::memory_order_relaxed));
        return next_write == read_index_.load(std::memory_order_acquire);
    }

    size_t size() const {
        size_t write = write_index_.load(std::memory_order_acquire);
        size_t read = read_index_.load(std::memory_order_acquire);
        
        if (write >= read) {
            return write - read;
        } else {
            return Size - read + write;
        }
    }

private:
    size_t next_index(size_t current) const {
        return (current + 1) % Size;
    }

    T buffer_[Size];
    std::atomic<size_t> read_index_;
    std::atomic<size_t> write_index_;
};

// 演示使用示例
void demo_usage() {
    std::cout << "=== 环形缓冲区演示 ===" << std::endl;
    
    // 创建容量为5的环形缓冲区
    CircularBuffer<int> buffer(5);
    
    // 基本操作演示
    std::cout << "缓冲区容量: " << buffer.capacity() << std::endl;
    std::cout << "初始大小: " << buffer.size() << std::endl;
    std::cout << "是否为空: " << buffer.empty() << std::endl;
    
    // 写入数据
    for (int i = 1; i <= 5; ++i) {
        if (buffer.push(i)) {
            std::cout << "写入: " << i << std::endl;
        }
    }
    
    std::cout << "写入5个数据后大小: " << buffer.size() << std::endl;
    std::cout << "是否已满: " << buffer.full() << std::endl;
    
    // 尝试写入第6个数据(应该失败)
    if (!buffer.push(6)) {
        std::cout << "写入失败 - 缓冲区已满" << std::endl;
    }
    
    // 读取数据
    int value;
    while (buffer.pop(value)) {
        std::cout << "读取: " << value << std::endl;
    }
    
    std::cout << "读取所有数据后大小: " << buffer.size() << std::endl;
    std::cout << "是否为空: " << buffer.empty() << std::endl;
}

// 生产者-消费者演示
void producer_consumer_demo() {
    std::cout << "\n=== 生产者-消费者演示 ===" << std::endl;
    
    CircularBuffer<int> buffer(10);
    std::atomic<bool> stop_producer(false);
    std::atomic<bool> stop_consumer(false);
    
    // 生产者线程
    std::thread producer([&]() {
        for (int i = 1; i <= 15; ++i) {
            if (buffer.push_blocking(i, std::chrono::milliseconds(100))) {
                std::cout << "生产: " << i << std::endl;
            } else {
                std::cout << "生产超时: " << i << std::endl;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        }
        stop_producer.store(true);
    });
    
    // 消费者线程
    std::thread consumer([&]() {
        int value;
        while (!stop_producer.load() || !buffer.empty()) {
            if (buffer.pop_blocking(value, std::chrono::milliseconds(200))) {
                std::cout << "消费: " << value << std::endl;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        stop_consumer.store(true);
    });
    
    producer.join();
    consumer.join();
    
    std::cout << "生产者-消费者演示结束" << std::endl;
}

// 性能测试
void performance_test() {
    std::cout << "\n=== 性能测试 ===" << std::endl;
    
    const int ITERATIONS = 1000000;
    CircularBuffer<int> buffer(1000);
    
    auto start = std::chrono::high_resolution_clock::now();
    
    std::thread producer([&]() {
        for (int i = 0; i < ITERATIONS; ++i) {
            while (!buffer.push(i)) {
                std::this_thread::yield();
            }
        }
    });
    
    std::thread consumer([&]() {
        int value;
        for (int i = 0; i < ITERATIONS; ++i) {
            while (!buffer.pop(value)) {
                std::this_thread::yield();
            }
            // 验证数据完整性
            if (value != i) {
                std::cerr << "数据损坏: 期望 " << i << ", 得到 " << value << std::endl;
            }
        }
    });
    
    producer.join();
    consumer.join();
    
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    std::cout << "处理 " << ITERATIONS << " 个元素耗时: " 
              << duration.count() << " ms" << std::endl;
    std::cout << "吞吐量: " 
              << (ITERATIONS * 1000.0 / duration.count()) << " 操作/秒" << std::endl;
}

int main() {
    try {
        demo_usage();
        producer_consumer_demo();
        performance_test();
        
        // 无锁缓冲区演示
        std::cout << "\n=== 无锁环形缓冲区演示 ===" << std::endl;
        LockFreeCircularBuffer<int, 5> lock_free_buffer;
        
        for (int i = 1; i <= 3; ++i) {
            if (lock_free_buffer.push(i)) {
                std::cout << "无锁缓冲区写入: " << i << std::endl;
            }
        }
        
        int value;
        while (lock_free_buffer.pop(value)) {
            std::cout << "无锁缓冲区读取: " << value << std::endl;
        }
        
    } catch (const std::exception& e) {
        std::cerr << "错误: " << e.what() << std::endl;
        return 1;
    }
    
    return 0;
}

关键注意事项

1. 线程安全设计

  • 互斥锁保护 :使用 std::mutex 保护共享数据
  • 条件变量 :使用 std::condition_variable 实现高效的等待通知机制
  • 内存序:无锁版本中正确使用内存序保证数据一致性

2. 空满判断策略

cpp 复制代码
// 方法1:使用计数变量(推荐)
bool empty() const { return count_ == 0; }
bool full() const { return count_ == capacity_; }

// 方法2:使用标志位
bool empty() const { return !is_full_ && (read_pos_ == write_pos_); }
bool full() const { return is_full_; }

3. 指针管理

cpp 复制代码
// 指针前进
read_pos_ = (read_pos_ + 1) % capacity_;
write_pos_ = (write_pos_ + 1) % capacity_;

4. 异常安全

  • 构造函数验证参数有效性
  • 使用 RAII 管理资源
  • 提供强异常安全保证

编译和运行

使用以下命令编译:

bash 复制代码
g++ -std=c++11 -pthread -O2 circular_buffer.cpp -o circular_buffer

运行:

bash 复制代码
./circular_buffer

性能优化建议

  1. 缓存友好:确保数据连续存储,提高缓存命中率
  2. 减少锁竞争:使用细粒度锁或无锁设计
  3. 批量操作:支持批量读写减少锁开销
  4. 内存预分配:避免动态内存分配

适用场景

  • 数据流处理(音频、视频流)
  • 生产者-消费者模式
  • 网络数据包缓冲
  • 实时系统数据交换
  • 日志记录系统

这个实现提供了完整的环形缓冲区功能,包括线程安全、阻塞/非阻塞操作、异常安全等特性,可以直接在生产环境中使用。

相关推荐
码事漫谈7 小时前
不止于Linux:百花齐放的开源世界与社区的力量
后端
绝无仅有8 小时前
某游戏大厂的常用面试问题解析:Netty 与 NIO
后端·面试·架构
donotshow8 小时前
DBeaver连接本地MySQL、创建数据库表的基础操作
java·后端
绝无仅有8 小时前
某游戏大厂的 Redis 面试必问题解析
后端·算法·面试
Moonbit8 小时前
月报 Vol.05:alias系统更新,新增 ReadOnlyArray 与 external iterator
后端·编程语言·编译器
王元_SmallA8 小时前
Go环境搭建(vscode调试)
java·后端
用户616160785659 小时前
使用nestjs框架构建一个RAG条例类文本分块系统
后端
用户4973573379810 小时前
MySQL/Redis等6大数据库,在7种Java业务中的选型与调优
后端