环形队列
【简介】
ring buffer / circular buffer 又名环形队列 / 环形缓冲区,其通过开辟固定尺寸的内存来实现反复复用同一块内存的目的。由于预先开辟了固定尺寸的内容,所以当数据满的时候,可以有两种处理方式,具体使用哪一种按照实际需求,具体如下:
1)当队列满的时候,新来的数据会覆盖最古老的数据,这种数据结构的特点是数据的写入不会因为队列满了而停止,同时也会导致旧数据的丢失,常用在一些对老旧数据不敏感的场景。如果数据很重要且不希望被丢弃,那么不要使用这种覆盖的模式,比如 流媒体场景下,每一帧数据都要确保完整地被渲染出来,不然会出现跳帧和音画同步无法完成等问题,所以不适合这种模式。
2)当数据满的时候,block 或者禁止写入操作,直到队列有空间时再允许写入。这种模式下可以保证数据不被覆盖掉。
【实现】
boost库直接有现成的,参考 VS2015编译并配置boost 64位 - 夕西行 - 博客园 (cnblogs.com)
也可以自己实现,思路参考:
[common c/c++] ring buffer/circular buffer 环形队列/环形缓冲区_c++ ringbuffer-CSDN博客
多线程环形缓冲区 - IAmAProgrammer - 博客园 (cnblogs.com)
环形队列本质上是一个定长的数组,两个指针在数组上指来指去。满足FIFO(先进先出)原则,在逻辑上是环形的(队列满时,队尾指针指向头的上一位)。参考自 详解环形队列_简述环形队列的优缺点-CSDN博客
简单封装
#include <array> template<typename T,const unsigned int N> class RingQueue { public: RingQueue():head(0),tail(0){} private: std::array<T, N> data; unsigned int head; unsigned int tail; public: bool push(const T& item) //入队 { if (!full()) { tail = (tail + 1) % N; data[tail] = item; return true; } return false; } bool peek(T& item) //查看队首元素 { if (!empty()) { item = data[(head + 1) % N]; return true; } return false; } bool pop() //出队,弹出队首元素 { if (!empty()) { head = (head + 1) % N; return true; } return false; } bool full() const //是否满 { return (tail + 1) % N == head ? true : false; } bool empty() const //是否空 { return head == tail ? true : false; } unsigned int size() const //元素个数 { return (tail - head + N) % N; } }; #include <iostream> int main() { RingQueue<std::pair<unsigned char, unsigned long long>, 5> rq; rq.push(std::make_pair(255, 12345)); rq.push(std::make_pair(10, 54321)); std::pair<unsigned char, unsigned long long> temp; while (rq.peek(temp)) { std::cout << (int)temp.first << " " << temp.second << "\n"; std::cout << rq.size() << "\n"; rq.pop(); } std::cout << rq.size() << "\n"; return 0; }
一个消费者、一个生产者案例 C++多线程的生产者消费者问题: Queue(队列)和环形缓存(RingBuffer)的效率对比-CSDN博客
多生产者、多消费者案例 详解C++高性能无锁队列的原理与实现 - 知乎 (zhihu.com)
队列满时,阻塞与非阻塞版本 超越99%的程序员,chatgpt用C++写个线程安全无锁环形队列 - 知乎 (zhihu.com)
【模仿写的环形队列】
使用了原子变量+内存屏障
Ringbuffer: 单生产者、单消费者的无锁环形队列 (gitee.com)
参考: