本说明涉及如下内容
1. 什么是环形缓冲区
2. 如何实现环形缓冲区
什么是环形缓冲区
环形缓冲区是一个先进先出(FIFO)的闭环的存储空间,可等效为首尾相连的队列。可类比为在内存中规划了一个水轮车(环形缓冲区),将水装载到轮子一侧的格子(将数据保存在缓冲区内存内),通过轮子的转动便可实现水(数据)在不同的水道(线程)内进行传输,从而实现数据在线程间的零拷贝传输。
ps. 因进程间会有各自独立的堆区,所以环形缓冲区无法实现进程间通讯。
如何实现环形缓冲区
一. 定义类成员变量
1. 为了方便项目使用,本套环形缓冲区采用泛型设计,定义待处理数据类型为DATA
template <typename DATA>
2. 定义保存数据的数组
std::vector<DATA> data;
3. 因为环形缓冲区在不同的线程间运作,故需要设计线程管理
/**
* @brief 读锁
*/
std::mutex m_read_lock;
/**
* @brief 写锁
*/
std::mutex m_write_lock;
/**
* @brief 用于避免空数据处理的条件变量
*/
std::condition_variable m_cv;
4. 定义缓冲区大小变量
/**
* @brief 缓冲区最大尺寸
*/
int max_size;
/**
* @brief 缓冲区现存数据量
*/
int count;
5. 定义缓冲区读写头
/**
* @brief 读头
*/
int head;
/**
* @brief 写头
*/
int end;
6. 定义缓冲区工作状态标识
bool is_working = false;
二. 定义缓冲区数据情况函数
/**
* @brief determine whether the buffer is empty
*
* @return true : no data in buffer
* @return false : buffer has data
*/
bool is_empty()
{
if (this->count == 0)
{
return true;
}
return false;
}
/**
* @brief is the buffer full
*
* @return true : full
* @return false : dissatisfaction
*/
bool is_full()
{
if (this->count == this->max_size)
{
return true;
}
return false;
}
三. 定义缓冲区构造函数
/**
* @brief Construct a new Circular Buffer object
*
* @param size : buffer size
*/
CircularBuffer(int size)
{
// 对缓冲区进行上锁,避免误操作
std::unique_lock<std::mutex> lock_w(this->m_write_lock);
std::unique_lock<std::mutex> lock_r(this->m_read_lock);
// 设置缓冲区尺寸
if (size <= 0)
{
Log_ERROR << "Buffer size must be greater than 0" << Log_END;
}
else
{
// 缓冲区预分配内存(通过此操作,避免std::vector重新分配内存,导致性能降低)
this->data.reserve(size);
// 正式为缓冲区分配内存
for (int i = 0; i < size; i++)
{
DATA process;
this->data.emplace_back(process);
}
// 缓冲区各成员变量初始化
this->max_size = size;
this->count = 0;
this->head = 0;
this->end = 0;
this->is_working = true;
}
}
四. 定义缓冲区数据量处理函数
/**
* @brief Get the Data Size object
*
* @return int
*/
int getDataSize()
{
std::unique_lock<std::mutex> lock_w(this->m_write_lock);
std::unique_lock<std::mutex> lock_r(this->m_read_lock);
if (this->is_working == false)
{
return -1;
}
return this->count;
}
/**
* @brief clear buffer
*
* @return true : clear successfully
* @return false : empty failed
*/
bool clearBuffer()
{
std::unique_lock<std::mutex> lock_w(this->m_write_lock);
std::unique_lock<std::mutex> lock_r(this->m_read_lock);
if (this->is_working == false)
{
Log_ERROR << "the buffer has not started working" << Log_END;
return false;
}
this->count = 0;
this->head = 0;
this->end = 0;
return true;
}
/**
* @brief Get the Max size object
*
* @return int
*/
int getMax_size()
{
return this->max_size;
}
五. 数据插入函数
/**
* @brief insert data into the buffer
*
* @param data : data to be added
* @return true : successfully added
* @return false : add failed
*/
bool insert(DATA data)
{
if (this->is_working == false)
{
return false;
}
std::unique_lock<std::mutex> lock(this->m_write_lock);
// 等待缓冲区有剩余空间,没有则在这里进行阻塞
this->m_cv.wait(lock, [this]
{ return this->is_full() == false; });
try
{
this->data[this->end] = data;
this->end = (this->end + 1) % this->max_size;
++this->count;
}
catch (const std::exception &e)
{
Log_ERROR << e.what() << Log_END;
}
lock.unlock();
this->m_cv.notify_one();
return true;
}
六. 数据弹出函数
/**
* @brief eject data from buffer
*
* @param data : container for receiving pop up data
* @return true : eject successfully
* @return false : eject failed
*/
bool eject(DATA &data)
{
if (this->is_working == false)
{
return false;
}
std::unique_lock<std::mutex> lock(this->m_read_lock);
this->m_cv.wait(lock, [this]
{ return this->is_empty() == false; });
try
{
data = this->data[this->head];
this->head = (this->head + 1) % this->max_size;
--this->count;
}
catch (const std::exception &e)
{
Log_ERROR << e.what() << Log_END;
}
lock.unlock();
this->m_cv.notify_one();
return true;
}
完整代码
#pragma once
/**
* @file CircularBuffer.hpp
* @author jiyilin 2474803745@qq.com
* @brief this is a program for creating and using multithreaded circular buffer
* @version 0.1
* @date 2023-07-26
*
* @copyright Copyright (c) 2023
*
*/
#include "log_system/LoggingSystem.h"
#include <condition_variable>
#include <mutex>
#include <vector>
#include <chrono>
#include <thread>
namespace Memory
{
/**
* @brief this class is used to use ring buffers
*
* @tparam DATA data
*
* @memberof data : ring buffer for saving data
* @memberof m_read_lock : read lock
* @memberof m_write_lock : write lock
*
* @memberof m_cv : condition variable to determine whether reading and writing can continue
*
* @memberof max_size : maximum value
* @memberof count : existing data volume
* @memberof head : buffer read header
* @memberof end : buffer write header
* @memberof is_working : identification of whether the buffer works normally
*/
template <typename DATA>
class CircularBuffer
{
private:
std::vector<DATA> data;
std::mutex m_read_lock;
std::mutex m_write_lock;
std::condition_variable m_cv;
int max_size;
int count;
int head;
int end;
bool is_working = false;
private:
/**
* @brief determine whether the buffer is empty
*
* @return true : no data in buffer
* @return false : buffer has data
*/
bool is_empty()
{
if (this->count == 0)
{
return true;
}
return false;
}
/**
* @brief is the buffer full
*
* @return true : full
* @return false : dissatisfaction
*/
bool is_full()
{
if (this->count == this->max_size)
{
return true;
}
return false;
}
public:
/**
* @brief Construct a new Circular Buffer object
*
* @param size : buffer size
*/
CircularBuffer(int size)
{
std::unique_lock<std::mutex> lock_w(this->m_write_lock);
std::unique_lock<std::mutex> lock_r(this->m_read_lock);
if (size <= 0)
{
Log_ERROR << "Buffer size must be greater than 0" << Log_END;
}
else
{
this->data.reserve(size);
for (int i = 0; i < size; i++)
{
DATA process;
this->data.emplace_back(process);
}
this->max_size = size;
this->count = 0;
this->head = 0;
this->end = 0;
this->is_working = true;
}
}
/**
* @brief Get the Data Size object
*
* @return int
*/
int getDataSize()
{
std::unique_lock<std::mutex> lock_w(this->m_write_lock);
std::unique_lock<std::mutex> lock_r(this->m_read_lock);
if (this->is_working == false)
{
return -1;
}
return this->count;
}
/**
* @brief clear buffer
*
* @return true : clear successfully
* @return false : empty failed
*/
bool clearBuffer()
{
std::unique_lock<std::mutex> lock_w(this->m_write_lock);
std::unique_lock<std::mutex> lock_r(this->m_read_lock);
if (this->is_working == false)
{
Log_ERROR << "the buffer has not started working" << Log_END;
return false;
}
this->count = 0;
this->head = 0;
this->end = 0;
return true;
}
/**
* @brief Get the Max size object
*
* @return int
*/
int getMax_size()
{
return this->max_size;
}
/**
* @brief insert data into the buffer
*
* @param data : data to be added
* @return true : successfully added
* @return false : add failed
*/
bool insert(DATA data)
{
std::unique_lock<std::mutex> lock(this->m_write_lock);
if (this->is_working == false)
{
return false;
}
this->m_cv.wait(lock, [this]
{ return this->is_full() == false; });
try
{
this->data[this->end] = data;
this->end = (this->end + 1) % this->max_size;
++this->count;
}
catch (const std::exception &e)
{
Log_ERROR << e.what() << Log_END;
}
lock.unlock();
this->m_cv.notify_one();
return true;
}
/**
* @brief eject data from buffer
*
* @param data : container for receiving pop up data
* @return true : eject successfully
* @return false : eject failed
*/
bool eject(DATA &data)
{
std::unique_lock<std::mutex> lock(this->m_read_lock);
if (this->is_working == false)
{
return false;
}
this->m_cv.wait(lock, [this]
{ return this->is_empty() == false; });
try
{
data = this->data[this->head];
this->head = (this->head + 1) % this->max_size;
--this->count;
}
catch (const std::exception &e)
{
Log_ERROR << e.what() << Log_END;
}
lock.unlock();
this->m_cv.notify_one();
return true;
}
/**
* @brief Destroy the Circular Buffer object
*
*/
~CircularBuffer(){};
};
} // namespace Memory
调用demo
# include "log_system/LoggingSystem.h"
# include "CircularBuffer/CircularBuffer.hpp"
void WriteThread(Memory::CircularBuffer<int *> *buffer)
{
for (int i = 0; i < 100; i++)
{
int *process = new int;
*process = i;
buffer->insert(process);
}
}
void TestThread(Memory::CircularBuffer<int *> *buffer)
{
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
if (buffer->getDataSize() != 0)
{
std::cout << "buffer size = " << buffer->getDataSize() << std::endl;
}
}
}
int main()
{
Memory::CircularBuffer<int *> buffer(10);
std::thread writeThreader(WriteThread, &buffer);
std::thread testThreader(TestThread, &buffer);
int *output;
while (true)
{
if (buffer.eject(output) == true)
{
std::cout << *output << std::endl;
}
}
return 0;
}