Implementation of circular queue in C + + buffer pool

Time:2022-5-14

Undertake the task of last week. The purpose effect is that the writing thread writes the data to the buffer, and the reading thread obtains the data in the buffer.

buffer

data structure

The so-called buffer is to open up a section of memory space to save data. The main attributes include the memory space for storing data, the length of the buffer and the length used. The corresponding method is to write data into the buffer, read data from the buffer, and set the length of the written buffer. The established data structure is:

class Buffer {
private:
    USHORT* buffer;    //  buffer
    int maxSize;    //  Maximum buffer length
    int effectiveSize;    //  Length used
public:
    Buffer(int bufferSize);        //  Set buffer size

    void setEffectiveSize(int size);    //  Set buffer used data

    void write(std::function<int(USHORT*, int)> const& writeBuffer);    //  Write data to buffer

    void read(std::function<void(USHORT*, int, int)> const& readBuffer);    //  Read data from buffer

    ~Buffer();
};

herewriteMethods andreadThe parameters accepted by the method arec++11Definedlambdaexpression. examplestd::function<int(USHORT*, int)> const& writeBufferRepresents an incominglambdaExpression, the accepted parameter isUSHORT*, int

Concrete implementation

  1. Constructor implementation:
    Pass the buffer size as a parameter to the constructor, apply for memory space in the constructor, and set the corresponding properties.
  2. writeandreadFunction implementation
    Pass in buffer data and buffer length as parameterslambdaExpression parameters and call.
Buffer::Buffer(int size) {
    this->maxSize = size;
    this->effectiveSize = 0;
    this->buffer = new USHORT[size];
}

void Buffer::setEffectiveSize(int size) {
    this->effectiveSize = size;
}

/**
*Writebuffer: lambda expression, the accepted parameter is USHORT *: buffer data, int: maximum length of buffer, return int: length of written data
*/
void Buffer::write(std::function<int(USHORT*, int)> const& writeBuffer) {
    this->effectiveSize = writeBuffer(this->buffer, this->maxSize);
}

/**
*Readbuffer: lambda expression, the accepted parameter is USHORT *: buffer data int: effective buffer length int: maximum buffer length, return void
*/
void Buffer::read(std::function<void(USHORT*, int, int)> const& readBuffer) {
    readBuffer(this->buffer, this->effectiveSize, this->maxSize);
}

For the same buffer,writeandreadOperations should be mutually exclusive, otherwise it will lead to data disorder. Therefore, the implementation of semaphore is needed to ensurewriteandreadMutually exclusive.

Semaphore implementation

For C + + semaphores, please refer to this article:C + + concurrent programming (VI): semaphore

class Semaphore {
private:
    std::mutex mutex;    //  mutex 
    std::condition_ variable cv;    //  Conditional variable
    int count;    //  Number of available resources
public:
    Semaphore(int count = 0);

    void singal();    //  Free a resource
        
    void wait();    //  Wait for a resource
};

Semaphore::Semaphore(int count) {
    If (count < 0) {throw "available resources cannot be less than 0";}
    this->count = count;
}

/**
*Release resources
*/
void Semaphore::singal() {
    std::unique_lock<std::mutex> lock(this->mutex);
    ++this->count;
    this->cv.notify_one();
}

/**
*Apply for resources
*/
void Semaphore::wait() {
    std::unique_lock<std::mutex> lock(this->mutex);
    this->cv. wait(lock, [=] { return count > 0; });        //  Execute downward when reutrn is true
    --this->count;
}

Perfect buffer

Add semaphores to the buffer data structure:

class Buffer {
private:
    ......
    Semaphore* sem;        //  Use semaphores to ensure that buffers are mutually exclusive
};

staywriteandreadMethod uses semaphores to achieve mutual exclusion.

Buffer::Buffer(int size) {
    .......
    this->sem = new Semaphore(1);
}

/**
*Write data to buffer, mutually exclusive operation
*Writebuffer: lambda expression, the accepted parameter is USHORT *: buffer data, int: maximum length of buffer, return int: length of written data
*/
void Buffer::write(std::function<int(USHORT*, int)> const& writeBuffer) {
    this->sem->wait();
    this->effectiveSize = writeBuffer(this->buffer, this->maxSize);
    this->sem->singal();
}

/**
*Read buffer data, mutually exclusive operation
*Readbuffer: lambda expression, the accepted parameter is USHORT *: buffer data int: effective buffer length int: maximum buffer length, return void
*/
void Buffer::read(std::function<void(USHORT*, int, int)> const& readBuffer) {
    this->sem->wait();
    readBuffer(this->buffer, this->effectiveSize, this->maxSize);
    this->sem->singal();
}

buffer pool

A buffer pool is actually a collection of buffers. Allocate buffers through buffer pools.

data structure

/**
*Buffer pool definition, storing and allocating buffers
*/
class BufferPool {
private:
    int head, tail;    //  Head and tail pointer
    Buffer** buffers;    //  buffer pool
    int total, lenth;    //  Total number of buffers and the number of buffers used

public:
    BufferPool(int count = 10, int bufferSize = DEFAULT_BUFFER_SIZE);  //  Constructor count: number of buffers buffersize: buffer size

    Buffer* getBuffer();    //  Get a buffer

    Buffer* popBuffer();    //  Get header buffer and pop

    bool empty();        //  Is the buffer pool empty

    bool full();        //  Is the buffer pool full

    ~BufferPool();

};

Concrete implementation

To ensure that the data is read out in the order of data writing, the buffer pool should be designed as a queue to ensure that the buffer is always written first when reading, and the buffer obtained when writing is the last buffer. At the same time, in order to ensure the recycling of the buffer, the buffer pool is designed as a circular queue.

For circular queues, there are two cases when the head pointer and tail pointer are equal. One is that the queue is empty (no buffer is allocated) and the other is that the queue is full (all buffers are allocated). There are generally two solutions. The first is to sacrifice a storage space. When the next bit pointed by the tail pointer is the head pointer, that is, the queue is full. The other is to add flag bits to judge the status of the current queue when the head and tail pointers are the same.

Due to the large setting space of one buffer zone in this project, the second method is adopted to increaselenthProperty indicates the number of buffers currently in use, which is used to judge whether the queue is empty or full.

BufferPool::BufferPool(int count, int bufferSize) {
    this->head = 0;
    this->tail = 0;
    this->lenth = 0;
    this->total = count;

    this->buffers = new Buffer*[count];
    for (int i = 0; i < count; i ++) {
        this->buffers[i] = new Buffer(bufferSize);
    }
}

/**
*Gets a buffer that overwrites old data when the buffer pool is full
*/
Buffer* BufferPool::getBuffer() {
    Buffer* buffer = this->buffers[this->tail];
    //The tail pointer points to the next buffer. If the current buffer pool is full, the head pointer moves down
    this->tail = (this->tail + 1) % this->total;
    this->lenth++;
    if (this->lenth > this->total) {
        this->head = (this->head + 1) % this->total;
        this->lenth = this->total;
    }
    return buffer;
}

/**
*Get header buffer and pop
*/
Buffer* BufferPool::popBuffer() {
    If (this - > lenth = = 0) {throw "buffer pool is empty";}
    Buffer* buffer = this->buffers[this->head];
    this->head = (this->head + 1) % this->total;
    this->lenth--;
    return buffer;
}

BufferPool::~BufferPool() {
    for (int i = 0; i < this->total; i ++) {
        delete this->buffers[i];
        this->buffers[i] = NULL;
    }
    delete this->buffers;
    this->buffers = NULL;
}

bool BufferPool::empty() {
    return this->lenth == 0;
}

bool BufferPool::full() {
    return this->lenth == this->total;
}

test

According to the last report, get the buffer from the buffer pool and write the data to the buffer.

//Use buffer pool to save data
BufferPool* bufferPool = new BufferPool();
//Get a buffer and write ad data
bufferPool->getBuffer()->write([&](USHORT* buffer, int maxSize) {
    If (! Acts1000_readdevicead (hdevice, buffer, maxsize, & nretsizewords, & navailsampspoints, 5.0)) // collect data and save the data to adbuffer. Nretsizewords represents the number of points actually read
    {
        printf("ReadDeviceDmaAD error...\n");
        _getch();
    }
    return nRetSizeWords;
});

Get buffer data:

while (!bufferPool->empty()) {
    bufferPool->popBuffer()->read([&](USHORT* ADBuffer, int eff, int maxSize) {
        for (int Index = 0; Index < 2; Index++)
        {
            printf("%d:%hu", Index, ADBuffer[Index]);
        }
    });
}