Кольцевой буфер в Qt С++

Кольцевой буфер - буфер фиксированного размера, часто применяемый в шаблоне "Поставщик - Потребитель" (Producer-Сonsumer), когда, например, нужно организовать взаимодействие двух асинхронных друг к другу процесса - запись (Producer) и чтение (Сonsumer). Принцип работы кольцевого буфера - когда буфер заполнен, новые данные пишутся в начало. Отсюда и название. В общем, типичное решение в потоковой обработке данных, с которой я столкнулся при проектировании архитектуры SDR приложения UnoSDR. Вместо кольцевого буфера можно применять очередь (FIFO) QQueue - но при этом уменьшается производительность из - за не фиксированного размера буфера. Если потребитель и поставщик находятся в разных потоках, совместный доступ к буферу нужно защитить с помощью мьютексов, которые не обьязательно должны быть в самом классе.

Ниже представлен класс кольцевого буфера, реализованный на С++ с применением фреймворка Qt.

class RingBuffer : public QObject
{
    Q_OBJECT

public:
    explicit RingBuffer(quint32 _size, QObject *parent = 0);
    ~RingBuffer();

    void clean();
    bool read(float *data, quint32 len);
    bool write(float *data, quint32 len);
    float getSample();
    void addSample(float sample);
    quint32 availableRead(); // available size read data
    quint32 availableWrite(); // available size write data

private:
    quint32 size;
    quint32 mask;
    quint32 idxRead;
    quint32 idxWrite;
    quint32 dataCount; // available read data size
    float *buf = NULL;
};
RingBuffer::RingBuffer(int type, quint32 _size, QObject *parent)
    : QObject(parent),
      size(_size)
{
    mask = size - 1;
    fbuf = (float*) malloc(sizeof(float) * size);
    clean();
}

RingBuffer::~RingBuffer()
{
    free(fbuf);
}

void RingBuffer::clean()
{
    idxRead = 0;
    idxWrite = 0;
    dataCount = 0;
}

bool RingBuffer::write(float *data, quint32 len)
{
    if(availableWrite() < len)
        return false;

    dataCount += len;

    for(int i = 0; i < len; i++)
    {
        fbuf[idxWrite++] = data[i];
        idxWrite &= mask;
    }

    return true;
}

bool RingBuffer::read(float *data, quint32 len)
{
    if(availableRead() < len)
        return false;

    dataCount -= len;

    for(int i = 0; i < len; i++)
    {
        data[i] = fbuf[idxRead++];
        idxRead &= mask;
    }

    return true;
}

float RingBuffer::getSample()
{
    dataCount --;
    float sample = fbuf[idxRead++];
    idxRead &= mask;
    return sample;
}

void RingBuffer::addSample(float sample)
{
    dataCount ++;
    fbuf[idxWrite++] = sample;
    idxWrite &= mask;
}

quint32 RingBuffer::availableRead()
{
    return dataCount;
}

quint32 RingBuffer::availableWrite()
{
    return size - dataCount;
}

Размер буфера должен быть равен степени двойки. Для оптимизации быстродействия, в методах read и write в цикле for можно убрать наложение маски mask, и присваивать новые значения указателем на позицию чтения и записи другим путем.

Нормальным функционированием буфера считается, когда указатель на запись не догоняет указатель на чтение, т.е. потребитель успевает прочитать все данные, сгенерированные поставщиком, а поставщику есть куда писать данные. Другими словами скорость чтения должна быть равна скорости записи информации. И конечно же, нужно отлавливать и обрабатывать ситуации, когда данный принцип нарушается, что бы программа вела себя предсказуемо. Для этого в классе есть два метода - availableRead и availableWrite, которые возвращают количество доступных данных для чтения и размер области буфера, доступный для записи. Эти методы нужно вызывать всякий раз, когда вы пользуетесь другими двумя методами - getSample и addSample. Для оптимизации быстродействия, проверки были выкинуты из классов, так как они могут применятся в цикле. Если нужна запись или чтение блока данных, то можно использовать методы write и read, где уже встроена проверка.