使用Qt信号量实现单生产者多消费者模型

最近学习Qt网络编程时需要用到单生产者多消费者模型,由于socket传输的数据通常被包装成各种不同的结构体(以下都称为消息),在不同的线程中生产和消费,因此考虑使用Qt的信号量编写一个消息队列用于不同线程之间的数据传递,实现单生产者——多消费者模型。

 可参考:


目录

消息结构

消息队列

改进消息队列


消息结构

通常生产者生产出的数据类型都为char*(如读取文件线程使用QFile读出的数据),即使用一个指针buf指向该数据的内存。然而在数据传输之前还需要将数据的信息和数据一起打包发送(如数据长度),因此,发送的数据通常被包装成各种形式的结构体。如下为一种包装形式最简单的结构体MSG_PACK,该消息仅包含数据的指针和长度。

//消息MSG结构体
typedef struct
{
    char* buf;      //数据指针
    int bufLen;     //数据长度
}MSG_PACK;

消息队列

本文使用QQueue类作为消息队列的容器,使用char型指针指向传入的结构体(也可使用模板),这里需注意的是结构体的字节对齐。若想修改结构体的字节对齐方式(如将结构体字节对齐修改成1),可在第一个结构体前添加:

#pragma pack(1)

并在最后一个结构体后添加:

#pragma pack()

恢复默认字节对齐方式。

blockmsgqueue.h

#ifndef BLOCKMSGQUEUE_H
#define BLOCKMSGQUEUE_H

#include <QSemaphore>
#include <QMutex>
#include <QQueue>

class blockMsgQueue
{
public:
    blockMsgQueue(const int& maxMsgsNum, const unsigned int& msgSize);
    ~blockMsgQueue();

    void addMsg(const char* msgPack);     //往消息队列添加消息msgPack
    void getMsg(char* msgPack);           //从消息队列取消息到msgPack


private:
    int m_maxMsgsNum;           //消息队列容量(最大消息数)
    unsigned int m_msgSize;     //消息大小
    QSemaphore *m_pFreeMsgs;    //信号量,队列剩余空间
    QSemaphore *m_pUsedMsgs;    //信号量,队列已使用空间
    QQueue<char*> m_queue;      //队列
    QMutex m_mutex;             //互斥锁
};

#endif // BLOCKMSGQUEUE_H

blockmsgqueue.cpp

#include "blockmsgqueue.h"

blockMsgQueue::blockMsgQueue(const int& maxMsgsNum, const unsigned int& msgSize)
    : m_maxMsgsNum(maxMsgsNum),
      m_msgSize(msgSize)
{
    m_pFreeMsgs = new QSemaphore(maxMsgsNum);
    m_pUsedMsgs = new QSemaphore(0);
}

blockMsgQueue::~blockMsgQueue()
{
    
}

void blockMsgQueue::addMsg(const char* msgPack)
{
    m_pFreeMsgs->acquire(1);    //队列满时阻塞
    m_mutex.lock();             //使用互斥锁,生产者加入数据时消费者不能取出
    char* msg = new char[m_msgSize];
    memcpy(msg, msgPack, m_msgSize);
    m_queue.enqueue(msg);
    m_mutex.unlock();
    m_pUsedMsgs->release(1);
}

void blockMsgQueue::getMsg(char* msgPack)
{
    m_pUsedMsgs->acquire(1);    //队列空时阻塞
    m_mutex.lock();             //使用互斥锁,多个消费者不能同时取出
    char *pack = m_queue.dequeue();
    memcpy(msgPack, pack, m_msgSize);
    m_mutex.unlock();
    m_pFreeMsgs->release(1);
}

main.cpp

#include <QCoreApplication>
#include <blockmsgqueue.h>
#include <QThread>
#include <QDebug>
#include <QtCore>


const int msgsNum = 170;        //消息数量
const int maxQueueSize = 10;    //消息队列容量

int comsumer_i = 0;             //消费者线程
static int takei = 0;           //消费者线程计数

//消息MSG结构体
typedef struct
{
    char* buf;              //数据
    unsigned int bufLen;    //数据长度
}MSG_PACK;

//消息队列
blockMsgQueue queue(maxQueueSize, sizeof(MSG_PACK));

[生产者]
class Producer : public QThread
{
public:
    void run() override
    {
        //生产者生产170个消息
        for(int i=0; i<msgsNum; i++)
        {
            //模拟生产消息MSGMSG_PACK
            MSG_PACK pack;
            pack.bufLen = 8;
            pack.buf = new char[pack.bufLen];
            char str[8] = {0};
            for (int j = 0; j < pack.bufLen - 1; j++)
            {
                str[j] = "ABCDE12345"[QRandomGenerator::global()->bounded(10)];
            }
            memcpy(pack.buf, str, pack.bufLen);

            //加入消息队列
            queue.addMsg((char*)&pack);
            qDebug() << QString("produce pack %1:   %2")
                        .arg(i+1).arg(pack.buf);

            msleep(30);
        }
    }
};

[消费者]
class Consumer : public QThread
{
public:
    void run() override
    {
        comsumer_i++;
        int c_i = comsumer_i;
        for(int i=0; i<msgsNum; i++)
        {
            MSG_PACK pack;
            queue.getMsg((char*)&pack);
            takei++;
            qDebug() << QString("comsumer %1 take pack %2:  %3")
                        .arg(c_i).arg(takei).arg(pack.buf);
            delete[] pack.buf;
            pack.buf = nullptr;
            msleep(100);
        }
    }
};


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Producer producer;      //生产者
    Consumer consumer1;     //消费者
    Consumer consumer2;
    Consumer consumer3;
    producer.start();
    consumer1.start();
    consumer2.start();
    consumer3.start();
    producer.wait();
    consumer1.wait();
    consumer2.wait();
    consumer3.wait();
    return a.exec();
}

本程序模拟生产170个消息存入消息队列,在消息传入队列时进行了类型转换,将消息的指针转换为char型指针,并在消息队列内部进行内存拷贝,这样便可适应于不同的结构体(这里笔者试过不进行拷贝而是直接将指针添加进队列,程序崩掉了...)。使用完消息后记得将消息里buf指向的内存delete掉。

运行结果如图:

改进消息队列

上一节所实现的消息队列每次在有新的消息进入队列时都会new一块内存进行拷贝,而从消息队列中取消息时通常是在其它线程(可能不止一个),在使用完消息后若忘记释放则最终会导致内存溢出(这是比较容易出现的问题),这里比较好的一个解决办法就是循环使用同一块内存,即在消息队列(类)构造时就根据其容量和大小分配好内存空间,每次从该队列中取出一个消息后又重新将其加入队列尾,达到一个循环使用内存的状态。

要实现这种机制需要增加一个空队列,在类构造时初始化空队列的内存空间,每次向消息队列中添加消息时都是对空队列中的内存空间进行拷贝,使用完后又重新将指向该内存空间的指针加入空队列的队尾,具体代码如下。

blockmsgqueue.h

#ifndef BLOCKMSGQUEUE_H
#define BLOCKMSGQUEUE_H

#include <QSemaphore>
#include <QMutex>
#include <QQueue>

class blockMsgQueue
{
public:
    blockMsgQueue(const int& maxMsgsNum, const unsigned int& msgSize);
    ~blockMsgQueue();

    void addMsg(const char* msgPack);     //往消息队列添加消息msgPack
    void getMsg(char* msgPack);           //从消息队列取消息到msgPack

private:
    int m_maxMsgsNum;           //消息队列容量(最大消息数)
    unsigned int m_msgSize;     //消息大小
    QSemaphore *m_pFreeMsgs;    //信号量,队列剩余空间
    QSemaphore *m_pUsedMsgs;    //信号量,队列已使用空间 
    QQueue<char*> m_freeQueue;  //空队列
    QQueue<char*> m_queue;      //消息队列
    QMutex m_mutex;             //互斥锁
};

#endif // BLOCKMSGQUEUE_H

blockmsgqueue.cpp

#include "blockmsgqueue.h"
#include <QDebug>

blockMsgQueue::blockMsgQueue(const int& maxMsgsNum, const unsigned int& msgSize)
    : m_maxMsgsNum(maxMsgsNum),
      m_msgSize(msgSize)
{
    m_pFreeMsgs = new QSemaphore(maxMsgsNum);
    m_pUsedMsgs = new QSemaphore(0);

    //初始化空队列
    for(int i=0; i<m_maxMsgsNum; i++)
    {
        char* freeMsg = new char[m_msgSize];
        m_freeQueue.enqueue(freeMsg);
    }
}

blockMsgQueue::~blockMsgQueue()
{
    //释放信号量
    if(m_pFreeMsgs)
    {
        m_pFreeMsgs = nullptr;
    }
    if(m_pUsedMsgs)
    {
        m_pUsedMsgs = nullptr;
    }

    //释放队列的内存
    QQueue<char*>::iterator ite1;
    QQueue<char*>::iterator ite2;
    for(ite1 = m_freeQueue.begin(); ite1 != m_freeQueue.end(); ++ite1)
    {
        delete[] (*ite1);        //delete[] freeMsg
        (*ite1) = nullptr;
    }
    for(ite2 = m_queue.begin(); ite2 != m_queue.end(); ++ite2)
    {
        delete[] (*ite2);        //delete[] freeMsg
        (*ite2) = nullptr;
    }
}

void blockMsgQueue::addMsg(const char* msgPack)
{
    m_pFreeMsgs->acquire(1);    //队列满时阻塞
    m_mutex.lock();        //使用互斥锁,生产者加入数据时消费者不能取出
    char* msgIn = m_freeQueue.dequeue();  //从空队列取出一段指向消息内存的指针
    memcpy(msgIn, msgPack, m_msgSize);
    m_queue.enqueue(msgIn);
    m_mutex.unlock();
    m_pUsedMsgs->release(1);
}

void blockMsgQueue::getMsg(char* msgPack)
{
    m_pUsedMsgs->acquire(1);    //队列空时阻塞
    m_mutex.lock();       //使用互斥锁,多个消费者不能同时取出
    char *msgOut = m_queue.dequeue();
    memcpy(msgPack, msgOut, m_msgSize);
    m_freeQueue.enqueue(msgOut);  //将消息重新加入空队列,消息指向的内存重复使用
    m_mutex.unlock();
    m_pFreeMsgs->release(1);
}

消息队列析构时再统一释放内存空间,不再由外部进行操作。

本文所实现的消息队列的具体应用可参考我的另一篇博客:https://blog.csdn.net/qq_38318941/article/details/102651562

有问题和建议欢迎一起学习讨论!

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt提供了QSemaphore类来实现信号量功能,可以用于实现一个生产者多个消费者的场景。 首先,我们需要创建一个QSemaphore对象,作为生产者消费者之间共享的信号量。在这个例子中,我们假设生产者线程负责生产产品,消费者线程负责消费产品。 生产者的逻辑如下: 1. 首先,获取信号量的锁定,如果信号量的计数器为0,则阻塞等待信号量的释放; 2. 当信号量的计数器不为0时,生产者线程开始生产产品,并进行相应的操作; 3. 完成产品的生产后,释放信号量的锁定,并增加信号量的计数器。 消费者的逻辑如下: 1. 首先,获取信号量的锁定,如果信号量的计数器为0,则阻塞等待信号量的释放; 2. 当信号量的计数器不为0时,消费者线程开始消费产品,并进行相应的操作; 3. 完成产品的消费后,释放信号量的锁定,并增加信号量的计数器。 下面是一个简化的示例代码: ```cpp QSemaphore semaphore; // 创建信号量对象 QVector<QString> products; // 存放产品的容器 // 生产者线程函数 void producer() { while (true) { semaphore.acquire(); // 获取信号量的锁定 // 生产产品的操作 QString product = generateProduct(); products.append(product); semaphore.release(); // 释放信号量的锁定 QThread::sleep(1); // 等待一段时间 } } // 消费者线程函数 void consumer() { while (true) { semaphore.acquire(); // 获取信号量的锁定 // 消费产品的操作 if (!products.isEmpty()) { QString product = products.takeFirst(); consumeProduct(product); } semaphore.release(); // 释放信号量的锁定 QThread::sleep(1); // 等待一段时间 } } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // 创建生产者消费者线程 QThread producerThread; QThread consumerThread1; QThread consumerThread2; QThread consumerThread3; // 信号量关联线程 semaphore.setInitialValue(1); semaphore.moveToThread(&producerThread); semaphore.moveToThread(&consumerThread1); semaphore.moveToThread(&consumerThread2); semaphore.moveToThread(&consumerThread3); // 启动线程 producerThread.start(); consumerThread1.start(); consumerThread2.start(); consumerThread3.start(); return a.exec(); } ``` 在上面的示例代码中,我们创建了一个QSemaphore对象作为信号量,并使用`acquire()`和`release()`函数来获取和释放信号量的锁定。生产者线程通过`acquire()`函数获取信号量的锁定,如果信号量的计数器为0,则线程会阻塞等待信号量的释放。消费者线程也使用相同的方法来获取和释放信号量的锁定。 这种方式实现了一个生产者多个消费者的场景,多个消费者线程可以在信号量的控制下轮流进行产品的消费。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值