一个应用程序一般只有一个线程,一个线程内的操作是顺序执行的,如果有某个比较消耗时间的计算或操作,比如网络通信中的文件传输,在一个线程内操作时,用户界面就可能会冻结而不能及时响应。这种情况下,可以创建一个单独的线程来执行比较消耗时间的操作,并与主线程之间处理好同步与数据交互,这就是多线程应用程序
。
Qt 为多线程操作提供了完整的支持。QThread 是线程类,是实现多线程操作的核心类,一般从QThread 继承定义自己的线程类。
线程之间的同步是其交互的主要问题
,Qt 提供了 QMutex、QMutexLocker、QReadWriteLock、QwaitCondition、QSemaphore 等多种类用于实现线程之间的同步。
Qt 还有 Qt Concurrent 模块,提供一些高级的 API 实现多线程编程而无需使用 QMutex、QwaitCondition 和QSemaphore 等基础操作。使用Qt Concurrent 实现的多线程程序可以自动根据处理器内核个数调整线程个数。
QThread会起一个子线程,并可以通过信号槽将变量传递到主线程中。
QThread 类提供不依赖于平台的管理线程的方法。一个 QThread 类的对象管理一个线程,一般从QThread 继承一个自定义类,并重定义虚函数run(),在run()函数里实现线程需要完成的任务。
将应用程序的线程称为主线程,额外创建的线程称为工作线程。一般在主线程里创建工作线程,并调用 start()开始执行工作线程的任务。start()会在内部调用 run()函数,进入工作线程的事件循环
,在 run()函数里调用 exit()或 quit()可以结束线程的事件循环,或在主线程里调用 terminate()强制结束线程。
QThread 类的主要接口函数、信号和槽函数见下表。
QThread 是 QObject 的子类,所以可以使用信号与槽机制。QThread 自身定义了 started()和finished()两个信号,started()信号在线程开始执行之前发射,也就是在 run()函数被调用之前,finished0信号在线程就要结束时发射。
在进行本章的学习前,建议先复习C++新特性中对应部分,Qt这里就是对C++11的语法进行封装。
作为实例,定义一个掷骰子的线程类QDiceThread,类的声明部分如下:
#ifndef QDICETHREAD_H
#define QDICETHREAD_H
#include
class QDiceThread : public QThread
{
Q_OBJECT
private:
int m_seq=0;//掷骰子次数序号
int m_diceValue;//骰子点数
bool m_Paused=true; //掷一次骰子
bool m_stop=false; //停止线程
protected:
void run() Q_DECL_OVERRIDE; //线程任务
public:
QDiceThread();
void diceBegin();//掷一次骰子
void dicePause();//暂停
void stopThread(); //结束线程
signals:
void newValue(int seq,int diceValue); //产生新点数的信号
};
#endif // QDICETHREAD_H
重载虚函数 run(),在此函数里完成线程的主要任务。
自定义 diceBegin()、dicePause()、stopThread()3 个公共函数用于线程控制,这3 个函数由主线程调用。
定义了一个信号 newValue(int seq,int diceValue) 用于在掷一次子得到新的点数之后发射此信号,由主线程的槽函数响应以获取值。
QDiceThread 类的实现代码如下:
#include "qdicethread.h"
#include
QDiceThread::QDiceThread()
{
}
void QDiceThread::diceBegin()
{ //开始掷骰子
m_Paused=false;
}
void QDiceThread::dicePause()
{//暂停掷骰子
m_Paused=true;
}
void QDiceThread::stopThread()
{//停止线程
m_stop=true;
}
void QDiceThread::run()
{//线程任务
m_stop=false;//启动线程时令m_stop=false
m_seq=0; //掷骰子次数
qsrand(QTime::currentTime().msec());//随机数初始化,qsrand是线程安全的
while(!m_stop)//循环主体
{
if (!m_Paused)
{
m_diceValue=qrand(); //获取随机数
m_diceValue=(m_diceValue % 6)+1;
m_seq++;
emit newValue(m_seq,m_diceValue); //发射信号
}
msleep(500); //线程休眠500ms
}
// 在 m_stop==true时结束线程任务
quit();//相当于 exit(0),退出线程的事件循环
}
其中,run()是线程任务的实现部分,线程开始就执行 run()函数。run()函数一般是事件循环过程,根据各种条件或事件处理各种任务。当run()函数退出时,线程的事件循环就结束了。
在run()函数里,初始化变量 m_stop 和m_seq,用qsrand()函数对随机数种子初始化。run()函数的主循环体是一个 while循环,在主线程调用 stopThread()函数使 m_stop 为 true,才会退出 while循环,调用quit()之后结束线程。
在 while 循环体内,又根据 m_Paused 判断当前是否需要掷子,如果需要掷骰子,则用随机函数生成一次子的点数 m_diceValue,然后发射信号 newValue(),将 m seq和m diceValue作为信号参数传递出去。主线程可以设计槽函数与此信号关联,获取这两个值并进行显示。
使用QDiceThread 类,设计一个应用程序 samp13_1,程序运行界面如下图所示。
窗体上方的几个按钮用于控制线程的启动与停止,控制开始与暂停掷骰子。中间的文本框显示次数和点数,右边根据点数显示资源文件里面的一个图片,图片存储在项目的资源文件里。下方的一个标签根据QDiceThread 的 started()和finished()两个信号显示线程的状态。
窗口类是从QDialog 继承的类 Dialog,其类定义如下(省略了按钮槽函数的定义):
#ifndef DIALOG_H
#define DIALOG_H
#include
#include "qdicethread.h"
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
private:
QDiceThread threadA;
protected:
void closeEvent(QCloseEvent *event);
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private slots:
...
private:
Ui::Dialog *ui;
};
#endif // DIALOG_H
这里定义了一个QDiceThread 类型的变量 threadA,重定义了 closeEvent()事件,自定义了3个槽函数。
Dialog类的构造函数代码如下:
Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
{//构造函数
ui->setupUi(this);
connect(&threadA,SIGNAL(started()),this,SLOT(onthreadA_started()));
connect(&threadA,SIGNAL(finished()),this,SLOT(onthreadA_finished()));
connect(&threadA,SIGNAL(newValue(int,int)),this,SLOT(onthreadA_newValue(int,int)));
}
构造函数主要是将 threadA 的 3 个信号与 Dialog 自定义的3 个槽函数相关联,这3 个槽函数的代码如下:
void Dialog::onthreadA_started()
{//线程的started()信号的响应槽函数
ui->LabA->setText("Thread状态:thread started");
}
void Dialog::onthreadA_finished()
{//线程的 finished()信号的响应槽函数
ui->LabA->setText("Thread状态:thread finished");
}
void Dialog::onthreadA_newValue(int seq,int diceValue)
{//QDiceThread的newValue()信号的响应槽函数,显示骰子次数和点数
QString str=QString::asprintf("第 %d 次掷骰子,点数为:%d",seq,diceValue);
ui->plainTextEdit->appendPlainText(str);
QPixmap pic; //图片显示
QString filename=QString::asprintf(":/dice/images/d%d.jpg",diceValue);
pic.load(filename);
ui->LabPic->setPixmap(pic);
}
started()信号发射时,表示线程开始执行,在标签里显示状态文字。
finished()信号发射时,表示线程结束执行,在标签里显示状态文字。
newValue()是 QDiceThread 定义的信号,在掷一次骰子获得新的点数后发射,将掷假子的次数和点数传递过来。槽函数onthreadA_newValue()获取这两个值并显示在文本框里,再根据点数从资源文件里获取相应的图片并显示。
窗口上5个按钮的代码如下:
void Dialog::on_btnStartThread_clicked()
{//启动线程 按钮
threadA.start();
ui->btnStartThread->setEnabled(false);
ui->btnStopThread->setEnabled(true);
ui->btnDiceBegin->setEnabled(true);
ui->btnDiceEnd->setEnabled(false);
}
void Dialog::on_btnDiceBegin_clicked()
{//开始 掷骰子按钮
threadA.diceBegin();
ui->btnDiceBegin->setEnabled(false);
ui->btnDiceEnd->setEnabled(true);
}
void Dialog::on_btnDiceEnd_clicked()
{//暂停 掷骰子按钮
threadA.dicePause();
ui->btnDiceBegin->setEnabled(true);
ui->btnDiceEnd->setEnabled(false);
}
void Dialog::on_btnStopThread_clicked()
{//结束线程 按钮
threadA.stopThread();//结束线程的run()函数执行
threadA.wait();//
ui->btnStartThread->setEnabled(true);
ui->btnStopThread->setEnabled(false);
ui->btnDiceBegin->setEnabled(false);
ui->btnDiceEnd->setEnabled(false);
}
void Dialog::on_btnClear_clicked()
{ //清空文本 按钮
ui->plainTextEdit->clear();
}
“启动线程”按钮调用线程的 start()函数,start()函数会内部调用 run()函数开始线程任务的执行。run()函数将内部变量 m_Paused 初始化为true,所以,启动线程后并不会立即开始掷散子。
“开始”按钮调用 diceBegin()函数,使 threadA 线程内部变量 m_Paused 变为 false,那么run()函数里就开始每隔 500 毫秒产生一次骰子点数,并发射信号 newValue()。
“暂停”按钮调用 dicePause()函数,使 threadA 线程内部变量 m_Paused 变为 true,run()函数里不再掷骰子,但是 run()函数并没有结束,也就是线程并没有结束。
“结束线程”按钮调用 stopThread()函数,使threadA 线程内部的 m_stop 变为 true,run()函数体的 while 循环结束,执行 quit()后线程结束。所以,线程结束就是 run()函数执行退出。
重载closeEvent()事件,在窗口关闭时确保线程被停止,代码如下:
void Dialog::closeEvent(QCloseEvent *event)
{ //窗口关闭事件,必须结束线程
if (threadA.isRunning())
{
threadA.stopThread();
threadA.wait();
}
event->accept();
}
#ifndef QDICETHREAD_H
#define QDICETHREAD_H
#include
class QDiceThread : public QThread
{
Q_OBJECT
private:
int m_seq=0;//掷骰子次数序号
int m_diceValue;//骰子点数
bool m_Paused=true; //掷一次骰子
bool m_stop=false; //停止线程
protected:
void run() Q_DECL_OVERRIDE; //线程任务
public:
QDiceThread();
void diceBegin();//掷一次骰子
void dicePause();//暂停
void stopThread(); //结束线程
signals:
void newValue(int seq,int diceValue); //产生新点数的信号
};
#endif // QDICETHREAD_H
#include "qdicethread.h"
#include
QDiceThread::QDiceThread()
{
}
void QDiceThread::diceBegin()
{ //开始掷骰子
m_Paused=false;
}
void QDiceThread::dicePause()
{//暂停掷骰子
m_Paused=true;
}
void QDiceThread::stopThread()
{//停止线程
m_stop=true;
}
void QDiceThread::run()
{//线程任务
m_stop=false;//启动线程时令m_stop=false
m_seq=0; //掷骰子次数
qsrand(QTime::currentTime().msec());//随机数初始化,qsrand是线程安全的
while(!m_stop)//循环主体
{
if (!m_Paused)
{
m_diceValue=qrand(); //获取随机数
m_diceValue=(m_diceValue % 6)+1;
m_seq++;
emit newValue(m_seq,m_diceValue); //发射信号
}
msleep(500); //线程休眠500ms
}
// 在 m_stop==true时结束线程任务
quit();//相当于 exit(0),退出线程的事件循环
}
#ifndef DIALOG_H
#define DIALOG_H
#include
#include "qdicethread.h"
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
private:
QDiceThread threadA;
protected:
void closeEvent(QCloseEvent *event);
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private slots:
void onthreadA_started();
void onthreadA_finished();
void onthreadA_newValue(int seq, int diceValue);
void on_btnClear_clicked();
void on_btnDiceEnd_clicked();
void on_btnDiceBegin_clicked();
void on_btnStopThread_clicked();
void on_btnStartThread_clicked();
private:
Ui::Dialog *ui;
};
#endif // DIALOG_H
#include "dialog.h"
#include "ui_dialog.h"
void Dialog::closeEvent(QCloseEvent *event)
{ //窗口关闭事件,必须结束线程
if (threadA.isRunning())
{
threadA.stopThread();
threadA.wait();
}
event->accept();
}
Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
{//构造函数
ui->setupUi(this);
connect(&threadA,SIGNAL(started()),this,SLOT(onthreadA_started()));
connect(&threadA,SIGNAL(finished()),this,SLOT(onthreadA_finished()));
connect(&threadA,SIGNAL(newValue(int,int)),this,SLOT(onthreadA_newValue(int,int)));
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::onthreadA_started()
{//线程的started()信号的响应槽函数
ui->LabA->setText("Thread状态:thread started");
}
void Dialog::onthreadA_finished()
{//线程的 finished()信号的响应槽函数
ui->LabA->setText("Thread状态:thread finished");
}
void Dialog::onthreadA_newValue(int seq,int diceValue)
{//QDiceThread的newValue()信号的响应槽函数,显示骰子次数和点数
QString str=QString::asprintf("第 %d 次掷骰子,点数为:%d",seq,diceValue);
ui->plainTextEdit->appendPlainText(str);
QPixmap pic; //图片显示
QString filename=QString::asprintf(":/dice/images/d%d.jpg",diceValue);
pic.load(filename);
ui->LabPic->setPixmap(pic);
}
void Dialog::on_btnClear_clicked()
{ //清空文本 按钮
ui->plainTextEdit->clear();
}
void Dialog::on_btnDiceEnd_clicked()
{//暂停 掷骰子按钮
threadA.dicePause();
ui->btnDiceBegin->setEnabled(true);
ui->btnDiceEnd->setEnabled(false);
}
void Dialog::on_btnDiceBegin_clicked()
{//开始 掷骰子按钮
threadA.diceBegin();
ui->btnDiceBegin->setEnabled(false);
ui->btnDiceEnd->setEnabled(true);
}
void Dialog::on_btnStopThread_clicked()
{//结束线程 按钮
threadA.stopThread();//结束线程的run()函数执行
threadA.wait();//
ui->btnStartThread->setEnabled(true);
ui->btnStopThread->setEnabled(false);
ui->btnDiceBegin->setEnabled(false);
ui->btnDiceEnd->setEnabled(false);
}
void Dialog::on_btnStartThread_clicked()
{//启动线程 按钮
threadA.start();
ui->btnStartThread->setEnabled(false);
ui->btnStopThread->setEnabled(true);
ui->btnDiceBegin->setEnabled(true);
ui->btnDiceEnd->setEnabled(false);
}
详细可见附带源码文件