qt 线程同步-条件等待(QWaitCondition)

        在多线程的程序中,多个线程之间的同步实际上就是它们之间的协调问题。前面使用锁的方式(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    <QObject>
#include    <QThread>

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 <QWaitCondition>
#include <QTime>
#include <QMutex>
#include <QDebug>
#include <QThread>
#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 <QObject>
#include <QTimer>
#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 <QDebug>
#include <QThread>
#include <unistd.h>

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 <QCoreApplication>
#include <QDebug>
#include <QThread>
#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
    }
  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

jingbo1801

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

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

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

打赏作者

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

抵扣说明:

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

余额充值