乐趣区

关于c++11:c缓冲池循环队列实现

承接上周工作,目标成果是写线程将数据写入缓冲区,读线程获取缓冲区中的数据。

缓冲区

数据结构

所谓缓冲区,就是开拓一段内存空间来保留数据,次要包含的属性为贮存数据的内存空间,缓冲区长度,已应用的长度。对应的办法为将数据写入缓冲区,从缓冲区中读入数据,设置已写入的缓冲区长度。所建设的数据结构为:

class Buffer {
private:
    USHORT* buffer;    // 缓冲区
    int maxSize;    // 缓冲区最大长度
    int effectiveSize;    // 曾经应用长度
public:
    Buffer(int bufferSize);        // 设置缓冲区大小

    void setEffectiveSize(int size);    // 设置缓冲区已用数据

    void write(std::function<int(USHORT*, int)> const& writeBuffer);    // 将数据写入缓冲区

    void read(std::function<void(USHORT*, int, int)> const& readBuffer);    // 从缓冲区中读取数据

    ~Buffer();};

这里 write 办法和 read 办法承受的参数为 c++11 定义的 lambda 表达式。例 std::function<int(USHORT*, int)> const& writeBuffer 代表传入一个 lambda 表达式,承受参数为USHORT*, int

具体实现

  1. 构造函数实现:
    将缓冲区大小作为参数传递给构造函数,在构造函数中申请内存空间,并设置相应属性。
  2. writeread 函数实现
    将缓冲区数据和缓冲区长度作为参数传入 lambda 表达式参数并调用。
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 表达式,承受参数为  USHORT*: 缓冲区数据 int: 缓冲区最大长度, 返回 int: 写入数据的长度
*/
void Buffer::write(std::function<int(USHORT*, int)> const& writeBuffer) {this->effectiveSize = writeBuffer(this->buffer, this->maxSize);
}

/**
* readBuffer: lambda 表达式,承受参数为  USHORT*: 缓冲区数据 int: 缓冲区无效长度 int: 缓冲区最大长度  返回 void
*/
void Buffer::read(std::function<void(USHORT*, int, int)> const& readBuffer) {readBuffer(this->buffer, this->effectiveSize, this->maxSize);
}

对于同一个缓冲区,writeread 操作因该是互斥的,否则就会导致数据错乱。因而须要信号量的实现来保障 writeread互斥。

信号量实现

对于 c ++ 信号量可参考这篇文章:C++ 并发编程(六):信号量(Semaphore)

class Semaphore {
private:
    std::mutex mutex;    // 互斥量
    std::condition_variable cv;    // 条件变量
    int count;    // 可用资源数
public:
    Semaphore(int count = 0);

    void singal();    // 开释一个资源
        
    void wait();    // 期待一个资源};

Semaphore::Semaphore(int count) {if (count < 0) {throw "可用资源不能小于 0";}
    this->count = count;
}

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

/**
* 申请资源
*/
void Semaphore::wait() {std::unique_lock<std::mutex> lock(this->mutex);
    this->cv.wait(lock, [=] {return count > 0;});        // reutrn true 时往下执行
    --this->count;
}

欠缺缓冲区

在缓冲区数据结构中退出信号量:

class Buffer {
private:
    ......
    Semaphore* sem;        // 应用信号量保障缓冲区应用互斥
};

writeread办法中应用信号量实现互斥。

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

/**
* 将数据写入缓冲区,互斥操作
* writeBuffer: lambda 表达式,承受参数为  USHORT*: 缓冲区数据 int: 缓冲区最大长度, 返回 int: 写入数据的长度
*/
void Buffer::write(std::function<int(USHORT*, int)> const& writeBuffer) {this->sem->wait();
    this->effectiveSize = writeBuffer(this->buffer, this->maxSize);
    this->sem->singal();}

/**
* 读取缓冲区数据,互斥操作
* readBuffer: lambda 表达式,承受参数为  USHORT*: 缓冲区数据 int: 缓冲区无效长度 int: 缓冲区最大长度  返回 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();}

缓冲池

缓冲池实际上就是缓冲区的汇合。通过缓冲池来调配缓冲区。

数据结构

/**
* 缓冲池定义,存储并调配缓冲区
*/
class BufferPool {
private:
    int head, tail;    // 头尾指针
    Buffer** buffers;    // 缓冲池
    int total, lenth;    // 缓冲区总个数和以应用个数

public:
    BufferPool(int count = 10, int bufferSize = DEFAULT_BUFFER_SIZE);  // 构造函数  count: 缓冲区个数  bufferSize: 缓冲区大小

    Buffer* getBuffer();    // 获取一个缓冲区

    Buffer* popBuffer();    // 获得头缓冲区并弹出

    bool empty();        // 缓冲池是否为空

    bool full();        // 缓冲池是否已满

    ~BufferPool();};

具体实现

要保障按数据写入的程序读出,应该把缓冲池设计为队列,保障读取时缓冲区总是最先写入的,同时写入时取得的缓冲区是最开端的缓冲区。同时为了保障缓冲区循环利用,将缓冲池设计为循环队列。

对于循环队列,当头指针和尾指针相等时,有两种状况。一种是队列为空(未调配缓冲区),另一种是队列已满(所有缓冲区都被调配)。解决办法个别有两种,第一种是就义一个存储空间,当尾指针指向的下一位为头指针时,即队列为满。另一种是减少标记位来判断当头尾指针雷同时,以后队列的状态。

因为本我的项目一个缓冲区设置空间较大,所以采纳第二种办法,减少 lenth 属性示意以后应用的缓冲区个数,用来判断队列为空或满。

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);
    }
}

/**
* 获取一个缓冲区 当缓冲池已满时,笼罩旧数据
*/
Buffer* BufferPool::getBuffer() {Buffer* buffer = this->buffers[this->tail];
    // tail 指针指向下一个缓冲区,如果以后缓冲池已满,头指针下移
    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;
}

/**
* 获取头缓冲区并弹出
*/
Buffer* BufferPool::popBuffer() {if (this->lenth == 0) {throw "缓冲池为空";}
    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;}

测试

依照上次汇报,从缓冲池中获取缓冲区,并将数据写入缓冲区。

// 用缓冲池来保留数据
BufferPool* bufferPool = new BufferPool();
// 获取一个缓冲区并将 AD 数据写入
bufferPool->getBuffer()->write([&](USHORT* buffer, int maxSize) {if (!ACTS1000_ReadDeviceAD(hDevice, buffer, maxSize, &nRetSizeWords, &nAvailSampsPoints, 5.0)) // 采集数据,将数据保留到 ADBuffer 中,nRetSizeWords 代表理论共读取了多少个点
    {printf("ReadDeviceDmaAD error...\n");
        _getch();}
    return nRetSizeWords;
});

获取缓冲区数据:

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]);
        }
    });
}
退出移动版