QT之使用 QWaitCondition 同步线程小例子

时间:2020-12-27 15:35:38

        接上一篇,本篇文章主要将介绍如何使用 QWaitCondition 来同步线程。

        学习 QWaitCondition 类之间,先研读下 Qt 文档对 QwaitCondition 的具体描述及介绍,大意如下:

        QWaitCondition 类提供了一个条件变量用于同步线程。

        QWaitCondition 允许一个线程告诉其他线程某些条件已被满足。一个或多个线程可以阻塞等待一个由 QWaitCondition 设定的条件 WakeOne() 或 WakeAll()。使用 WakeOne() 去唤醒一个随意选择的线程或者使用 WakeAll() 去唤醒他们所有。

        例如,假定当用于按下一个键我们有3条任务应该被执行,每一个任务都可以被分成一个线程,每一个都有一个 run() 主体,就像下面这样:

forever {
mutex.lock();
keyPressed.wait(&mutex);
do_something();
mutex.unlock();
}
        在这里,keyPressed 是一个 QWaitCondition 类型的全局变量。

        第4条线程可以读取键的按压,每次接受一次,就会唤醒其他3条线程,就像下面这样:

forever {
getchar();
keyPressed.wakeAll();
}
        这3条线程唤醒顺序是未定义的。另外,如果当键被按压仍有一些线程在执行 do_something() ,那么,键按压之后它们将不会被唤醒(因为它们没有等待条件变量)并且任务也不会去执行。这个问题可以使用计数器和 QMutex 去防护解决它。例如,这里有个新的代码用于工作线程:

forever {
mutex.lock();
keyPressed.wait(&mutex);
++count;
mutex.unlock();

do_something();

mutex.lock();
--count;
mutex.unlock();
}
        这里有个代码是用于第4条线程的:

forever {
getchar();

mutex.lock();
// Sleep until there are no busy worker threads
while (count > 0)
{
mutex.unlock();
sleep(1);
mutex.lock();
}
keyPressed.wakeAll();
mutex.unlock();
}
        互斥量是必须的,因为两条线程尝试同时修改相同变量的值,其结果是不可预知的。

        原始的同步线程中使用等待条件是强而有力的,Wait Conditions Example 这个例子显示了如何使用 QWaitCondition 替代 QSemaphore 去控制访问环形共享内存通过生产者线程和消费者线程。


        好吧,到此为止,我只是把 Qt 帮助文档翻译了一遍。


        现在上 Qt 自带样例代码:

【Producer.h】

#ifndef PRODUCER_H
#define PRODUCER_H
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <iostream>

extern QMutex mutex;
extern const int DataSize;
extern const int BufferSize;
extern char buffer[8];
extern int numUsedBytes;
extern QWaitCondition bufferNotEmpty;
extern QWaitCondition bufferNotFull;

class Producer : public QThread
{
public:
explicit Producer(QObject* parent = NULL);

virtual void run();
};

#endif // PRODUCER_H
【Producer.cpp】

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

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

}


void Producer::run()
{
qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));

for(int i = 0; i < DataSize; i++)
{
/*
* QMutex: The purpose of a QMutex is to protect an object,
* data structure or section of code so that only one thread can
* access it at a time
* QMutex可以抱回一个对象, 数据结构, 以及代码段在同一时刻只能有一条线程访问
*
* 锁住, 保护对变量的访问, 每次只有一条线程能访问
*/
mutex.lock();

//如果可用缓冲区是满的, 则堵塞当前线程, 等待消费者线程读取
if(numUsedBytes == BufferSize)
{
/*
* 释放互斥量, 并阻塞线程等待条件
* 解锁条件是: wakeOne() or wakeAll() 以及 timeout
*/
bufferNotFull.wait(&mutex);
}
mutex.unlock();

buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
std::cout << buffer[i % BufferSize] << std::endl;

//锁住变量 numUsedBytes
mutex.lock();
++numUsedBytes;
//通知生产者线程
bufferNotEmpty.wakeAll();
//解锁
mutex.unlock();
}
}


【Consumer.h】

#ifndef CONSUMER_H
#define CONSUMER_H
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <iostream>

extern QMutex mutex;
extern const int DataSize;
extern const int BufferSize;
extern char buffer[8];
extern int numUsedBytes;
extern QWaitCondition bufferNotEmpty;
extern QWaitCondition bufferNotFull;

class Consumer : public QThread
{
public:
explicit Consumer(QObject* parent = NULL);

virtual void run();

signals:
void stringConsumed(const QString& text);
};

#endif // CONSUMER_H
【Consumer.cpp】

#include "consumer.h"

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

}

void Consumer::run()
{
for (int i = 0; i < DataSize; ++i)
{
mutex.lock();
if (numUsedBytes == 0)
{
//如果可用缓存区数据为0, 则通知生产则线程更新数据, 并堵塞当前线程等待条件
bufferNotEmpty.wait(&mutex);
}
mutex.unlock();

std::cout << buffer[i % BufferSize] << std::endl;

//锁住并保护对变量的访问, 每次只有一条线程能访问
mutex.lock();
//清空计数
--numUsedBytes;
//唤醒生产者线程
bufferNotFull.wakeAll();
//解锁
mutex.unlock();
}
}


【main.cpp】

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

const int DataSize = 24;
const int BufferSize = 1;
char buffer[8];
int numUsedBytes = 0;

QMutex mutex;
QWaitCondition bufferNotEmpty;
QWaitCondition bufferNotFull;

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

Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();

return a.exec();
}

        执行结果:

QT之使用 QWaitCondition 同步线程小例子