C++ 笔记 —— 实现一个环形阻塞队列

Posted 湖广午王

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ 笔记 —— 实现一个环形阻塞队列相关的知识,希望对你有一定的参考价值。

实现原理

环形阻塞队列,顾名思义,首先,它是一个队列,然后,它应当是一个环形,并且它是会进行阻塞的。但是根据我们的常识,内存地址是用一个long long int来存储的,我们存储的数据的地址无法绕成一个环,所以我们想要成环的话,需要我们自己去处理。

如上图,相比环状实现的来说,数据在内存中的存储更接近线性实现那样。线性实现和环形实现中,我们都记录着队头、队尾。如果是一个内存中,数据可以存储为环形,那么我们只需要在写入数据的时候,注意写在队尾,读取数据时,从堆头开始读取就行了。但是很明显,数据在内存中无法存储位环形,所以我们还需要做以下处理:

  • 写入数据时,当需要写入的数据长度大于末尾上的可用存储空间时,需要把待写入的数据拆分成两部分,一部分写在末尾,一部分写在最前。
  • 读取数据时,当需要读取的数据长度大于末尾上的有效数据长度,且最前面还有有效数据时,需要读取末尾的数据,在读出最前面的一部分数据
  • 写入数据和读取数据后,需要及时的更新队头和队尾的游标,以便于下一次读取。
  • 为保证线程安全,避免同时写入和读取

另外我们需要实现的环形队列还需要有阻塞的功能。所以在,上面的基础上,我们还要做更多的处理:

  • 写入数据,如果需要写入的数据长度,大于剩余的可用的存储空间时,需要将可用的存储空间填满,然后等待有可用存储空间时,再写入还没有写入的数据到队列中。
  • 读取数据后,通知有可用的存储空间,以方便数据写入。

实现代码

根据实现原来,我们来用C++11进行实现,首先我们来定义头文件,为了这个队列可以存储各类数据,我们将队列定义为一个模板类,内容如下:

#pragma once

#include <cstdint>
#include <mutex>
#include <condition_variable>

template <typename T>
class CircleBlockQueue 
private:
	//用来存储数据
    T * data;
    //总容量
    uint64_t size;

	//用来记录读取位置的游标
    uint64_t readPos0;
    //用来记录写入位置的游标
    uint64_t writePos0;
    //当前队列中有效数据的长度
    uint64_t currentDataLength0;

	//用来实现阻塞的互斥锁及信号量
    std::mutex mutex;
    std::condition_variable capacityLock;

    bool clearFlagfalse;
	
    bool innerPush(T * data,uint64_t length);
public:
    explicit CircleBlockQueue(uint64_t size);
    ~CircleBlockQueue();
    //向队列中添加数据
    bool push(T * data,uint64_t length);
    //从队列中读出数据
    uint64_t pop(T * data,uint64_t length);
    //清除队列中的数据
    void clear();
    //队列数据长度
    uint64_t length();
;

#include "CircleBlockQueue.inl"

CircleBlockQueue.inl 具体的实现如下,相关细节见注释:

#include <cstdlib>
#include <cstring>

template <typename T>
CircleBlockQueue<T>::CircleBlockQueue(uint64_t size):size(size) 
    data = (T *)malloc(size * sizeof(T));


template <typename T>
CircleBlockQueue<T>::~CircleBlockQueue() 
    free(data);


template <typename T> bool CircleBlockQueue<T>::innerPush(T *t, uint64_t length) 
    if(length <= size - currentDataLength)
        //尾巴上的容量不足以存储期望push的数据时,需要将数据分成两段,一段在队尾,一段在队头
        //足够存储时,直接存储即可
        if(writePos + length > size)
            uint64_t remainLength = size - writePos;
            memcpy((void *)(this->data + writePos),(void *)t,remainLength* sizeof(T));
            memcpy((void *)(this->data),(void *)(t+remainLength),(length-remainLength) * sizeof(T));
            writePos = length + writePos - size;
            currentDataLength += length;
        else
            memcpy((void *)(this->data + writePos),(void *)t,length * sizeof(T));
            writePos += length;
            currentDataLength += length;
        
        return true;
    
    return false;


template <typename T>
bool CircleBlockQueue<T>::push(T *t, uint64_t length) 
    std::unique_lock<std::mutex> lck(mutex);
    T * dataPos = t;
    uint64_t currentLength = length;
    do
        if(!innerPush(dataPos,currentLength))
            //容量不足以够存储期望push的数据时,按照能力进行存储
            //然后等待有新的存储空间时,再次尝试存储
            uint64_t remainSize = size - currentDataLength;
            if(remainSize > 0)
                innerPush(dataPos,remainSize);
                currentLength -= remainSize;
                dataPos += remainSize;
            
            capacityLock.wait(lck);
        else
            break;
        
    while(!clearFlag);
    clearFlag = false;


template <typename T>
uint64_t CircleBlockQueue<T>::pop(T *t, uint64_t length) 
    std::unique_lock<std::mutex> lck(mutex);
    if(currentDataLength)
        //当前数据大于期望读取的数据长度
        uint64_t realReadLength = length;
        if(length > currentDataLength)
            realReadLength = currentDataLength;
        
        //被读取的数据不是在首尾两段
        if(size - readPos >= realReadLength)
            memcpy((void *)t,(void *)(this->data+readPos),realReadLength* sizeof(T));
            readPos += realReadLength;
            currentDataLength -= realReadLength;
        else
            uint64_t readLength = size - readPos;
            memcpy((void *)t,(void*)(this->data + readPos),readLength * sizeof(T));
            memcpy((void *)(t+readLength),(void *)(this->data),(realReadLength - readLength) * sizeof(T));
            readPos = readPos + realReadLength - size;
            currentDataLength -= realReadLength;
        
        capacityLock.notify_all();
        return realReadLength;
    
    return 0;


template <typename T>
void CircleBlockQueue<T>::clear() 
    std::unique_lock<std::mutex> lck(mutex);
    clearFlag = true;
    readPos = 0;
    writePos = 0;
    currentDataLength = 0;
    memset(data,0, size * sizeof(T));
    capacityLock.notify_all();


template <typename T>
uint64_t CircleBlockQueue<T>::length() 
    std::unique_lock<std::mutex> lck(mutex);
    return currentDataLength;


简单测试

实现了一个环形阻塞队列后,我们需要对实现进行测试,看看实现是否符合我们的预期:


void writeThread(CircleBlockQueue<char> * queue)
    while(threadFlag)
        queue->push(const_cast<char *>("hello"), 5);
    


void queueTest()
    CircleBlockQueue<char> testQueue(8);
    std::thread thread(writeThread,&testQueue);
    thread.detach();

    std::string cmd;
    while(cmd != "exit")
        std::cin>>cmd;
        if(cmd == "clear")
            testQueue.clear();
        else if(cmd == "exit")
            testQueue.clear();
            threadFlag = false;
        else
            char temp[3];
            testQueue.pop(temp, 3);
            std::cout <<"pop:"<< temp <<std::endl;
        
    
    testQueue.clear();


void main()
	queueTest();
	return 0;

其输入输出如下:

结合代码,从结果可以看到,push、pop、clear方法的结果都符合我们的预期,这样一个环形阻塞队列就实现完成了。


欢迎转载,转载请保留文章出处。湖广午王的博客[http://blog.csdn.net/junzia/article/details/88926431]


以上是关于C++ 笔记 —— 实现一个环形阻塞队列的主要内容,如果未能解决你的问题,请参考以下文章

C++ 笔记 —— 实现一个环形阻塞队列

什么是环形队列,采用什么方法实现环形队列

C++实现一个简单的环形队列

C++实现一个简单的环形队列

基于信号量与环形队列实现读写异步缓存队列

基于信号量与环形队列实现读写异步缓存队列