QT之使用QSemaphore信号量同步线程小例子

        接上一篇,本篇文章主要将介绍如何使用 QSemaphore 信号量来同步线程,以及两条线程间数据传递。

       首先,要理解信号量,它可以理解为对互斥量功能的扩展,一般来说,互斥量只能锁定一次而信号量可以获取多次,信号量典型是用来保护一定数量的同种资源。具体可以查看 Qt 文档。

       其次,要清楚线程中信号量运行过程,大致为:

       Step1. 生产者线程使用信号量获取 Free 区域存储空间

       Step2. 生产者线程更新 Free 区域数据

       Step3. 生产者线程将更新后的 Free 区域转换为可用的 Used 区域供消费者线程所使用

       Step4. 消费者线程获取 Used 区域存储空间

       Step5. 消费者线程读取 Used 区域的数据

       Step6. 消费者线程读取数据后将 Used 区域转换为 Free 区域供生产者线程所使用

       Step7. 循环Step1~Step6,直到数据传递完毕

       注意:一个区域转换为另一区域,就伴随着一个减少而另一个增加。譬如:Free -> Used,Free空间会变少,而Used一个会增加。


      废话不多说了,代码是最好的解释:

【Producer.h】

#ifndef PRODUCER_H
#define PRODUCER_H

#include <QObject>
#include <QSemaphore>

class Producer : public QObject
{
    Q_OBJECT
public:
    explicit Producer(QObject* parent = 0);

    ~Producer();

public slots:
    /*
     * 生产者线程向缓存器中写入数据, 直到它达到缓冲器的终点为止,
     * 然后它会再次从起点重新开始, 覆盖已存在的数据
    */
    virtual void write();

public:
    /*
     * 如果生产者生产的数据太快, 那么会把消费者还没读取的数据覆盖;
     * 如果消费者读取的数据太快, 那么会超过生产者而读取一些垃圾数据;
     * 为了使二者同步, 这里就使用了两个 QSemaphore 信号量来解决此问题
    */
    QSemaphore* freeSpace;

    QSemaphore* usedSpace;

    //生产者线程与消费者线程需传递数据的大小
    int dataSize;

    //共享空间区域大小
    int bufferSize;

    //指向共享空间区域
    char* buffer;
};

#endif // PRODUCER_H

【Producer.cpp】

#include "producer.h"
#include <QTime>
#include <iostream>
#include <QThread>


Producer::Producer(QObject* parent):QObject(parent)
{

}

Producer::~Producer()
{

}

void Producer::write()
{
    for(int i = 0; i < dataSize; i++)
    {//需要传递 dataSize 个数据, 所以需要循环 dataSize 次

        //谨慎的从free空间获取资源, 默认为1, 如果没有可用空间, 则堵塞线程直到有足够的空间
        freeSpace->acquire();

        //将随机出的字符存放至buffer内
        //注意随机数的产生, 要先使用 qsrand 产生, 接着使用 qrand 返回
        //休眠主要是防止种子一直, 导致随机数一直
        QThread::msleep(1000);
        qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
        buffer[i % bufferSize] = 65 + qrand() % 10;

        //输出随机产生的字符
        std::cout << (int)QThread::currentThreadId() << ": " << buffer[i % bufferSize] << std::endl;

        //从free空间获取的资源被使用之后, 即可看做是已使用的空间,
        //既然生产者线程已使用完, 则要释放给消费者线程使用
        usedSpace->release();
    }
}


【Consumer.h】

#ifndef CONSUMER_H
#define CONSUMER_H

#include <QObject>
#include <QSemaphore>

class Consumer : public QObject
{
    Q_OBJECT
public:
    explicit Consumer(QObject *parent = 0);

public slots:
    virtual void read();

public:
    QSemaphore* freeSpace;

    QSemaphore* usedSpace;

    //生产者线程与消费者线程需传递数据的大小
    int dataSize;

    //共享空间区域大小
    int bufferSize;

    //指向共享空间区域
    char* buffer;
};

#endif // CONSUMER_H
【Consumer.cpp】

#include "consumer.h"
#include <iostream>
#include <QThread>

Consumer::Consumer(QObject *parent) : QObject(parent)
{

}

void Consumer::read()
{
    for(int i = 0; i < dataSize; i++)
    {//因为要从生产者线程传递 dataSize 个数据, 所以要循环读取 dataSize 次

        //谨慎的从已被生产者线程初始化区域内读取数据
        usedSpace->acquire();

        //输出读取到的数据
        std::cout << (int)QThread::currentThreadId() << ": " << buffer[i % bufferSize] << std::endl;

        //消费者读取数据后, 要将内存释放, 以便生产者更新数据
        freeSpace->release();
    }
}


【main.cpp】

#include <QCoreApplication>
#include "producer.h"
#include "consumer.h"
#include <QThread>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    /*
     * 假设两条线程间传递的数据大小是 24,
     * 实际上我们分配的内存空间是 8,
     * 这时我们就可以通过 3 循环来完成数据传递
    */
    const int DataSize = 24;
    const int BufferSize = 8;
    char buffer[BufferSize] = {'\0'};

    /*
     * 使用 QSemaphore 信号量需要理解:
     * 1. freeSpace 信号量主要是控制生产者写入数据的那部分缓存区域
     * 2. usedSpace 信号量主要是控制消费者读取数据的那部分缓存区域
    */
    QSemaphore freeSpace(BufferSize);
    QSemaphore usedSpace(0);

    Producer producer;
    producer.freeSpace = &freeSpace;
    producer.usedSpace = &usedSpace;
    producer.dataSize = DataSize;
    producer.bufferSize = BufferSize;
    producer.buffer = buffer;

    Consumer consumer;
    consumer.freeSpace = &freeSpace;
    consumer.usedSpace = &usedSpace;
    consumer.dataSize = DataSize;
    consumer.bufferSize = BufferSize;
    consumer.buffer = buffer;

    QThread pt, ct;
    //注意, 要将对象移至线程内
    producer.moveToThread(&pt);
    consumer.moveToThread(&ct);

    //绑定信号槽
    QObject::connect(&pt, &QThread::started, &producer, &Producer::write);
    QObject::connect(&ct, &QThread::started, &consumer, &Consumer::read);

    pt.start();
    ct.start();

    return a.exec();
}

执行结果:






  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

l357630798

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值