QT多线程(一):QThread线程通信

QThread 是实现多线程的核心类,我们一般从 QThread 继承定义自己的线程类。线程之间的同步是线程交互的主要问题,Qt 提供了 QMutex、QWaitCondition、QSemaphore 等多个类用于实现线程同步。

QThread 类简介

一个 QThread 类的对象管理一个线程。在使用的时,需要从 QThread 继承定义线程类,并重定义 QThread 的虚函数 run(),在函数 run()里处理线程的事件循环。

应用程序通常被称为主线程,由应用线程使用QThread创建的其他线程被称为工作线程。在主线程里创建工作线程后,可以调用函数 start()开始执行工作线程的任务。函数 start()会在其内部调用函数 run()进入工作线程的事件循环,函数 run()的程序体一般是一个无限循环,可以在函数 run()里调用函数 exit()或 quit()结束线程的事件循环,或在主线程里调用函数 terminate()强制结束线程。

QThread类的主要接口函数如下表所示(省略了 const 关键字):

QT多线程(一):QThread线程通信_第1张图片

QT多线程(一):QThread线程通信_第2张图片

 示例程序解读

QT多线程(一):QThread线程通信_第3张图片

该示例演示了工作线程启动后会随机生成骰子点数,并通过发送信号与主线程通信,以便主线程更新图片界面(主线程将该线程的信号连接到了相应的槽函数)。

工作线程

创建一个TDiceThread类,继承自QThread,其头文件和cpp文件如下所示:

///.h头文件///
#ifndef TDICETHREAD_H
#define TDICETHREAD_H

#include    

class TDiceThread : public QThread
{
    Q_OBJECT
private:
    int     m_seq=0;        //掷骰子次数序号
    int     m_diceValue;    //骰子点数
    bool    m_paused=true;  //暂停次骰子
    bool    m_stop=false;   //停止线程run()
protected:
    void    run();      //线程的事件循环
public:
    explicit TDiceThread(QObject *parent = nullptr);

    void    diceBegin();    //开始掷骰子
    void    dicePause();    //暂停
    void    stopThread();   //结束线程run()
signals:
    void    newValue(int seq,int diceValue);    //产生新点数的信号
};

#endif // TDICETHREAD_H

.cpp文件///
#include    "tdicethread.h"
//#include    

#include    

TDiceThread::TDiceThread(QObject *parent)
    : QThread{parent}
{

}


void TDiceThread::diceBegin()
{ //开始掷骰子
    m_paused=false;
}

void TDiceThread::dicePause()
{//暂停掷骰子
    m_paused=true;
}

void TDiceThread::stopThread()
{//停止线程
    m_stop=true;
}

void TDiceThread::run()
{//线程的事件循环
    m_stop=false;       //启动线程时令m_stop=false
    m_paused=true;      //启动运行后暂时不掷骰子
    m_seq=0;            //掷骰子次数
    while(!m_stop)      //循环主体
    {
        if (!m_paused)
        {
            m_diceValue= QRandomGenerator::global()->bounded(1,7);  //产生随机数[1,6]
            m_seq++;
            emit newValue(m_seq, m_diceValue);  //发射信号
        }
        msleep(500);    //线程休眠500ms
    }

//  在  m_stop==true时结束线程任务
    quit();     //相当于exit(0), 退出线程的事件循环
}

run()函数是必须重新实现的,线程的任务就在这个函数里实现。TDiceThread 类定义了一个信号 newValue(),在掷一次骰子之后此信号会被发射。

在主线程中创建并调用函数 QThread::start()开始运行线程后,线程内部就会运行函数 run()。run()里面通常是一个无限循环,可以根据各种条件或事件进行相应的处理。本示例中添加了m_paused、m_stop变量来对线程进行控制,当函数 run()退出时,线程的事件循环就结束了。

主线程

主线程头文件如下:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include    "tdicethread.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT
private:
    TDiceThread   *threadA;     //工作线程
protected:
    void    closeEvent(QCloseEvent *event);
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private slots:
    void    do_threadA_started();
    void    do_threadA_finished();
    void    do_threadA_newValue(int seq, int diceValue);
//    void on_actStart_triggered();
    void on_actClear_triggered();
    void on_actThread_Run_triggered();
    void on_actThread_Quit_triggered();
    void on_actDice_Run_triggered();
    void on_actDice_Pause_triggered();
private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

在主窗口构造函数中,需要创建该线程,并连接started()、finished信号与自定义信号newValue,代码如下:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    threadA= new TDiceThread(this); 
    connect(threadA,&TDiceThread::started, this, &MainWindow::do_threadA_started);
    connect(threadA,&TDiceThread::finished,this, &MainWindow::do_threadA_finished);
    connect(threadA,&TDiceThread::newValue,this, &MainWindow::do_threadA_newValue);

//    connect(threadA,SIGNAL(started()), this,SLOT(do_threadA_started()));
//    connect(threadA,SIGNAL(finished()),this,SLOT(do_threadA_finished()));
//    connect(threadA,SIGNAL(newValue(int,int)),this,SLOT(do_threadA_newValue(int,int)));

}

started()信号,在函数 run()被调用之前此信号被发射,连接的槽函数,实现程序在状态栏里显示状态信息。

void MainWindow::do_threadA_started()
{//与线程的started()信号关联
    ui->statusbar->showMessage("Thread状态:thread started");
    ui->actThread_Run->setEnabled(false);
    ui->actThread_Quit->setEnabled(true);

    ui->actDice_Run->setEnabled(true);
}

finished()信号,在线程要结束时此信号被发射,对应的槽函数同样显示状态信息。


void MainWindow::do_threadA_finished()
{//与线程的finished()信号关联
    ui->statusbar->showMessage("Thread状态:thread finished");
    ui->actThread_Run->setEnabled(true);
    ui->actThread_Quit->setEnabled(false);

    ui->actDice_Run->setEnabled(false);
    ui->actDice_Pause->setEnabled(false);
}

用户自定义信号newValue(),在生成新的随机数的时候发送信号,并附带计数序号和骰子点数,在主窗口连接的槽函数对界面图像和文本进行更新。

void MainWindow::do_threadA_newValue(int seq, int diceValue)
{//与线程的newValue()信号关联
    QString  str=QString::asprintf("第 %d 次掷骰子,点数为:%d",seq,diceValue);
    ui->plainTextEdit->appendPlainText(str);

    QString filename=QString::asprintf(":/dice/images/d%d.jpg",diceValue);
    QPixmap pic(filename);      //载入图片
    ui->labPic->setPixmap(pic); //显示骰子图片
}

窗口上其他几个 Action函数实现对工作线程的控制

void MainWindow::on_actThread_Run_triggered()
{//"启动线程"按钮
    threadA->start();
}

void MainWindow::on_actThread_Quit_triggered()
{//"结束线程"按钮
    threadA->stopThread();
}


void MainWindow::on_actDice_Run_triggered()
{//"开始"按钮,开始掷骰子
    threadA->diceBegin();
    ui->actDice_Run->setEnabled(false);
    ui->actDice_Pause->setEnabled(true);
}

void MainWindow::on_actDice_Pause_triggered()
{//"暂停"按钮,暂停掷骰子
    threadA->dicePause();
    ui->actDice_Run->setEnabled(true);
    ui->actDice_Pause->setEnabled(false);
}

“启动线程”按钮调用线程的 start()函数,函数 start()内部会调用函数 run()开始线程任务的执 行。函数 run()将内部变量 m_paused 初始化为 true,所以,启动线程后并不会立即开始掷骰子。

“开始”按钮调用 TDiceThread::diceBegin()函数,使 threadA 线程内部变量 m_paused 变为 false, 因此函数 run()就每隔 500 毫秒生成一次骰子点数,并发射信号 newValue()。

“暂停”按钮调用 TDiceThread::dicePause()函数,使 threadA 线程内部变量 m_paused 变为 true, 因此函数 run()里不再实现掷骰子,但是函数 run()并没有退出,也就是线程并没有结束运行。

“结束线程”按钮调用 TDiceThread::stopThread()函数,使 threadA 线程内部变量 m_stop 变为 true,因此函数 run()内的 while 循环结束,运行 quit()后线程结束运行。所以,线程结束就是指函 数 run()退出。

另外,MainWindow 类还重定义了事件处理函数 closeEvent(),以确保在窗口关闭时线程被停止,如果没有设置,那么主线程被关闭结束后,工作线程可能还会继续进行无意义的运行并占用系统资源,代码如下:

void MainWindow::closeEvent(QCloseEvent *event)
{
    if (threadA->isRunning())
    {
        threadA->terminate();   //强制结束线程
        threadA->wait();        //等待线程结束
    }
    event->accept();
}

试了一下没有处理该事件时,会提示如下:

所以对于工作线程,主线程在close(点击x号关闭)时,一定对还在运行的线程进行手动关闭。

参考

QT6 C++开发指南

你可能感兴趣的:(QT,qt,开发语言,多线程,QThread)