接上一篇,本篇文章主要将介绍如何使用 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();
}
执行结果: