QThread 是实现多线程的核心类,我们一般从 QThread 继承定义自己的线程类。线程之间的同步是线程交互的主要问题,Qt 提供了 QMutex、QWaitCondition、QSemaphore 等多个类用于实现线程同步。
一个 QThread 类的对象管理一个线程。在使用的时,需要从 QThread 继承定义线程类,并重定义 QThread 的虚函数 run(),在函数 run()里处理线程的事件循环。
应用程序通常被称为主线程,由应用线程使用QThread创建的其他线程被称为工作线程。在主线程里创建工作线程后,可以调用函数 start()开始执行工作线程的任务。函数 start()会在其内部调用函数 run()进入工作线程的事件循环,函数 run()的程序体一般是一个无限循环,可以在函数 run()里调用函数 exit()或 quit()结束线程的事件循环,或在主线程里调用函数 terminate()强制结束线程。
QThread类的主要接口函数如下表所示(省略了 const 关键字):
该示例演示了工作线程启动后会随机生成骰子点数,并通过发送信号与主线程通信,以便主线程更新图片界面(主线程将该线程的信号连接到了相应的槽函数)。
创建一个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++开发指南