在多线程的程序中,多个线程之间的同步实际上就是它们之间的协调问题。前面使用锁的方式(QMutex和QMutexLocker、QReadWriteLock和QReadWriteLock、QWriteLocker)都有一个通用的问题:在一个线程解锁资源后,不能及时通知其他线程。
QWaitCondition提供了另外一种改进的线程同步方法,QWaitCondition与QMulex结合,可以使一个线程在满足一定条件时通知其他多个线程,使它们及时作出响应,这样比只使用互斥量效率要高一些。例如,threadDAQ在写满一个缓冲区之后,及时通知threadShow和threadSaveFile, 使它们可以及时读取缓冲区数据。
QWaitCondition提供如下一些函数:
• wait(QMutex *lockedMutex),解锁互斥量lockedMutex,并阻塞等待唤醒条件,被唤醒后锁定lockedMutex并退出函数;
• wakeAll(),唤醒所有处于等待状态的线程,线程唤醒的顺序不确定,由操作系统的调度策略决定;
• wakeOne(),唤醒一个处于等待状态的线程,唤醒哪个线程不确定,由操作系统的调度策 略决定。
QWaitCondition 一般用于“生产者/消费者”(producer/consumer)模型中。“生产备”产生数据,“消费者”使用数据,前述的数据采集、显示与存储的三线程例子就适用这种模型。
创建实例程序,将掷骰子的程序修改为producer/consumer模型,一个线程类QThreadProducer专门负责掷骰子产生点数;一个线程类QThreadConsumer专门及时读取数据,并送给主线程进行显示。这两个类定义在一个文件qmythread.h里,定义代码如下:
#ifndef QMYTHREAD_H
#define QMYTHREAD_H
//#include
#include
class QThreadProducer : public QThread
{
Q_OBJECT
private:
bool m_stop=false; //停止线程
protected:
void run() Q_DECL_OVERRIDE;
public:
QThreadProducer();
void stopThread();
};
class QThreadConsumer : public QThread
{
Q_OBJECT
private:
bool m_stop=false; //停止线程
protected:
void run() Q_DECL_OVERRIDE;
public:
QThreadConsumer();
void stopThread();
signals:
void newValue(int seq, int diceValue);
};
#endif // QMYTHREAD_H
QThreadProducer用于掷骰子,但是去掉了开始和暂停的功能,线程一启动就连续地掷骰子。QThreadConsumer用于读取掷骰子的次数和点数,并用发射信号方式把数据传递出去。这两个类的实现代码在一个文件qmythrcad.cpp里,下面是这两个类的实现代码的主要部分:
#include
#include
#include
#include
#include
#include qmythread.h
QMutex mutex;
QWaitCondition newdataAvailable;
int seq=0; // 序号
int diceValue;
QThreadProducer::QThreadProducer()
{
}
void QThreadProducer::stopThread()
{
QMutexLocker locker(&mutex);
m_stop=true;
}
void QThreadProducer::run()
{
qDebug()<< "QThreadProducer run(), tid : " << QThread::currentThreadId();
m_stop=false; // 启动线程时令m_stop=false
seq=0;
qsrand(QTime::currentTime().msec()); // 随机数初始化,qsrand是线程安全的
while(!m_stop) // 循环主体
{
mutex.lock();
diceValue=qrand(); // 获取随机数
diceValue=(diceValue % 6)+1;
seq++;
mutex.unlock();
qDebug()<< "QThreadProducer wakeAll() "<< "diceValue= "<< diceValue;
newdataAvailable.wakeAll();// 唤醒所有线程,有新数据了
msleep(500); // 线程休眠100ms
}
}
QThreadConsumer::QThreadConsumer()
{
}
void QThreadConsumer::stopThread()
{
QMutexLocker locker(&mutex);
m_stop=true;
}
void QThreadConsumer::run()
{
qDebug()<< "QThreadConsumer run(), tid :" << QThread::currentThreadId();
m_stop=false; // 启动线程时令m_stop=false
while(!m_stop) // 循环主体
{
mutex.lock();
newdataAvailable.wait(&mutex); // 已经拿到数据了,通知其它线程可用。会先解锁mutex,使其他线程可以使用mutex
emit newValue(seq, diceValue);
qDebug()<< "QThreadConsumer emit newValue()" << "seq= "<< seq << "diceValue=" << diceValue;
mutex.unlock();
// msleep(100); // 线程休眠100ms
}
}
掷骰子的次数和点数的变量定义为共享变量,这样两个线程都可以访问。定义了互斥量mutex,定义了 QWaitCondition实例newdataAvailable,表示有新数据可用了。
QThreadProducer::run()函数负责每隔500亳秒掷骰子产生一次数据,新数据产生后通过等待条件唤醒所有等待的线程,即:
newdataAvailable.wakeAll();
QThreadConsumer::run()函数中的while循环,首先需要将互斥量锁定,再执行下面的一条语句:
newdataAvailable.wait(&mutex);
这条语句以mutex作为输入参数,内部会首先解锁mutex,使其他线程可以使用mutex,newdataAvailable 进入等待状态。当 QThreadProducer 产生新数据使用 newdataAvailable.wakeAll() 唤醒所有线程后,newdataAvailable.wait(&mutex)会再次锁定mutex,然后退出阻塞状态,以执行后面的语句。
所以,使用QWaitCondition可以使QThreadConsumer线程的执行过程进入等待状态。在 QThreadProducer线程满足条件后,唤醒QThreadConsumer线程及时退出等待状态,继续执行后面的程序。
主函数类中进行调用。Process类头文件定义如下:
#ifndef PROCESS_H
#define PROCESS_H
#include
#include
#include "qmythread.h"
class Process: public QObject
{
Q_OBJECT
public:
Process();
private:
QThreadProducer threadProducer;
QThreadConsumer threadConsumer;
public:
void startThread();
void stopThread();
private slots:
void onthreadA_started();
void onthreadA_finished();
void onthreadB_started();
void onthreadB_finished();
void onthreadB_newValue(int seq, int diceValue);
};
#endif // PROCESS_H
定义两个线程类实例,并定义了几个槽函数。采用信号与槽的方式与threadConsumer建立通信并获取数据。Process.cpp定义如下:
#include "process.h"
#include
#include
#include
Process::Process()
{
connect(&threadProducer,SIGNAL(started()),this,SLOT(onthreadA_started()));
connect(&threadProducer,SIGNAL(finished()),this,SLOT(onthreadA_finished()));
connect(&threadConsumer,SIGNAL(started()),this,SLOT(onthreadB_started()));
connect(&threadConsumer,SIGNAL(finished()),this,SLOT(onthreadB_finished()));
connect(&threadConsumer,SIGNAL(newValue(int,int)),this,SLOT(onthreadB_newValue(int,int)));
qDebug()<< "Process Process(), tid :" << QThread::currentThreadId();
}
// 启动线程
void Process::startThread()
{
qDebug()<< "Process startThread(), tid :" << QThread::currentThreadId();
threadConsumer.start();
threadProducer.start();
}
// 停止线程
void Process::stopThread()
{
qDebug()<< "Process stopThread(), tid :" << QThread::currentThreadId();
if (threadProducer.isRunning())
{
threadProducer.stopThread();//结束线程的run()函数执行
threadProducer.wait();//
}
if (threadConsumer.isRunning())
{
threadConsumer.terminate(); //因为threadB可能处于等待状态,所以用terminate强制结束
threadConsumer.wait();//
}
}
void Process::onthreadA_started()
{
qDebug()<< "Process onthreadA_started(), tid : " << QThread::currentThreadId();
}
void Process::onthreadA_finished()
{
qDebug()<< "Process onthreadA_finished(), tid :" << QThread::currentThreadId();
}
void Process::onthreadB_started()
{
qDebug()<< "Process onthreadB_started(), tid : "<< QThread::currentThreadId();
}
void Process::onthreadB_finished()
{
qDebug()<< "Process onthreadB_finished(), tid :" << QThread::currentThreadId();
}
void Process::onthreadB_newValue(int seq, int diceValue)
{
qDebug()<< "Process onthreadB_newValue(), tid : "<< QThread::currentThreadId();
qDebug()<< "onthreadB_newValue()," << "seq=" << seq << "diceValue=" << diceValue;
}
两个线程启动的先后顺序不应调换,应先启动threadConsumer,使其先进入wait状态,后启动 threadProducer,这样在 threadProducer 里 wakeAll()时 threadconsumer 就可以及时响应,否则会 丢失第一次掷骰子的数据。
“结束线程”的代码如下:
结束线程时,若按照上面的顺序先结束threadProducer线程,则必须使用terminate。来强制结束threadConsumer线程,因为threadConsumer可能还处于条件等待的阻塞状态中,将无法正常结 束线程。
main.cpp调用:
#include
#include
#include
#include "process.h"
using namespace std;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<< "mian(), tid :" << QThread::currentThreadId();
Process proc;
// 启动线程
proc.startThread();
return a.exec();
}
运行结果:
关键代码段:
// QThreadProducer
while(!m_stop) // 循环主体
{
mutex.lock();
diceValue=qrand(); // 获取随机数
diceValue=(diceValue % 6)+1;
seq++;
mutex.unlock();
qDebug()<< "QThreadProducer wakeAll()" << "diceValue=" << diceValue;
newdataAvailable.wakeAll(); // 唤醒所有线程,有新数据了
msleep(500); // 线程休眠100ms
}
// QThreadConsumer
while(!m_stop) // 循环主体
{
mutex.lock();
newdataAvailable.wait(&mutex); // 已经拿到数据了,通知其它线程可用。会先解锁mutex,使其他线程可以使用mutex
emit newValue(seq, diceValue);
qDebug()<< "QThreadConsumer emit newValue()" << "seq"= << seq << "diceValue"= << diceValue;
mutex.unlock();
// msleep(100); // 线程休眠100ms
}