A circular buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end. It is typically used to buffer data streams. It is well suited to embedded systems because of the fixed-size buffer (i.e. the buffer is not dynamically resized).
The following example uses two condition variables to allow the reader and writer to wait on each other:
template<class T>
class CircularBuffer
{
public:
explicit CircularBuffer(int size) : buffer(size) {}
size_t size() const { return buffer.size(); }
void Write(const T& value)
{
std::unique_lock lock(mutex);
notFull.wait(lock, [this]() { return count < size(); });
buffer[writePos] = value;
if (count == 0)
notEmpty.notify_one();
++count;
writePos = (writePos+1) % size();
}
void Read(T& value)
{
std::unique_lock lock(mutex);
notEmpty.wait(lock, [this]() { return count > 0; });
if (count == size())
notFull.notify_one();
--count;
value = buffer[readPos];
readPos = (readPos+1) % size();
}
private:
std::mutex mutex;
std::condition_variable notEmpty;
std::condition_variable notFull;
std::vector<T> buffer;
size_t readPos = 0;
size_t writePos = 0;
size_t count = 0;
};
The following example shows how the write and read operations can be split into begin/end methods to avoid the overhead of copying the data items.
template<class T>
class DirectAccessCircularBuffer
{
public:
explicit DirectAccessCircularBuffer(int size) : buffer(size) {}
size_t size() const { return buffer.size(); }
T& BeginWrite()
{
std::unique_lock lock(mutex);
notFull.wait(lock, [this]() { return count < size(); });
return buffer[writePos];
}
void EndWrite()
{
std::lock_guard lock(mutex);
if (count == 0)
notEmpty.notify_one();
++count;
writePos = (writePos+1) % size();
}
const T& BeginRead()
{
std::unique_lock lock(mutex);
notEmpty.wait(lock, [this]() { return count > 0; });
return buffer[readPos];
}
void EndRead()
{
std::lock_guard lock(mutex);
if (count == size())
notFull.notify_one();
--count;
readPos = (readPos+1) % size();
}
private:
std::mutex mutex;
std::condition_variable notEmpty;
std::condition_variable notFull;
std::vector<T> buffer;
size_t readPos = 0;
size_t writePos = 0;
size_t count = 0;
};