上篇文章中简单介绍了如何使用 Windows API 和c++11中的 std::thread 创建线程。
线程的创建和基本使用
本篇文章将会介绍如何使用QThread创建线程。
- QThread是Qt所有线程控制的基础,每一个QThread实例对象控制一个线程。
- QThread可以直接实例化使用也可以用继承的方式使用,QThread以事件循环的方式,允许继承自QObject的槽函数在线程中被调用执行。子类化QThread可以在开启线程事件循环之前初始化一个新线程,或者不使用事件循环的方式执行并行代码。
QThread的入口执行函数是 run() 函数,默认 run() 函数会通过调用函数 exec() 开启事件循环在线程中。可以使用函数 QObject::moveToThread() 将一个工作对象与线程对象相关联。
下面是一个简单的示例,示例中在一个新线程中计算前n个数的和后通过信号返回给调用者:
工作类头文件, Worker.h
#ifndef WORKER_H
#define WORKER_H
#include
class Worker : public QObject
{
Q_OBJECT
public:
Worker(QObject* parent = nullptr);
~Worker();
public slots:
// 计算前count个数的和
void doWork(int count);
signals:
// 发送计算完成信号
void doFinished(int);
};
#endif
工作类CPP文件, Worker.cpp
#include "Worker.h"
#include
#include
Worker::Worker(QObject* parent)
:QObject(parent)
{
}
Worker::~Worker()
{
}
// 计算 0~count个数的和
void Worker::doWork(int count)
{
int sum = 0;
for (int i=0; i<=count; ++i)
sum += i;
// 打印当前函数名,线程ID,以及计算结果
qDebug() << __FUNCTION__ << "Thread ID: " << QThread::currentThreadId() << ", Result is " << sum;
emit doFinished(sum);
}
槽函数 void doWork(int count); 用来计算前count个数的和,计算完成后,发送信号 doFinished(int) 其中的参数是计算结果。这就是一个工作类,与线程一点关系没有。
接下来是控制器
控制器头文件,Controller.h
#ifndef MYOBJECT_H
#define MYOBJECT_H
#include
#include
class Controller : public QObject
{
Q_OBJECT
public:
Controller(QObject* parent = nullptr);
~Controller();
// 开启线程计算
void startThreadRunFunc(int number);
private:
QThread m_thread;
signals:
// 该信号用于触发工作者中的槽函数
void startCalcSum(int);
private slots:
// 接受计算完毕后的结果槽函数
void onCalcSumFinished(int sum);
};
#endif
控制器CPP文件,Controller.cpp
#include "Controller.h"
#include "Worker.h"
#include
Controller::Controller(QObject* parent)
:QObject (parent)
{
// [1]
Worker* worker = new Worker;
worker->moveToThread(&m_thread);
// [2]
QObject::connect(this, &Controller::startCalcSum, worker, &Worker::doWork);
// [3]
QObject::connect(worker, &Worker::doFinished, this, &Controller::onCalcSumFinished);
// [4] 当线程退出时,释放工作者内存
QObject::connect(&m_thread, &QThread::finished, worker, &Worker::deleteLater);
// [5]
m_thread.start();
}
Controller::~Controller()
{
m_thread.quit();
m_thread.wait();
}
void Controller::startThreadRunFunc(int number)
{
// 发送开始计算信号
emit startCalcSum(number);
qDebug() << __FUNCTION__ << " : Current Thread is " << QThread::currentThreadId();
}
void Controller::onCalcSumFinished(int sum)
{
// 打印行数名,当前线程ID,计算结果
qDebug() << __FUNCTION__ \
<< " : Current Thread is " << QThread::currentThreadId() \
<< ", Result is " << sum;
}
构造函数中,主要做了如下步骤:
main函数中代码如下:
#include
#include
#include "Controller.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 创建控制器
Controller *object = new Controller;
// 计算前100个数的和
object->startThreadRunFunc(100);
return a.exec();
}
执行结果如下:
Controller::startThreadRunFunc : Current Thread is 0x491c
Worker::doWork Thread ID: 0x62c0 , Result is 5050
Controller::onCalcSumFinished : Current Thread is 0x491c , Result is 5050
整体流程如下:
在Qt4.x的时候,QThread的常用方式是继承QThread重载函数 run() 。run() 函数是新线程的入口函数。我们同样完成上功能,代码如下:
CThread头文件:
#ifndef CTHREAD_H
#define CTHREAD_H
#include
#include
class CThread : public QThread
{
Q_OBJECT
public:
CThread(QObject* parent = nullptr);
~CThread();
// 线程入口函数
void run(void) override;
// 计算前 0 ~ number的和
void calcSum(int number);
private:
std::atomic<bool> m_startThread;
std::atomic<int> m_number;
signals:
// 发送计算完成信号
void doFinished(int);
private slots:
// 相应计算完成结果
void onDoFinished(int sum);
};
#endif
CThread源文件
#include "CThread.h"
#include
CThread::CThread(QObject* parent)
:QThread (parent)
,m_startThread(false)
,m_number(0)
{
QObject::connect(this, &CThread::doFinished, this, &CThread::onDoFinished);
this->start();
}
CThread::~CThread()
{
this->requestInterruption();
this->wait();
}
void CThread::run(void)
{
while (!this->isInterruptionRequested())
{
// 判断是否开启线程计算
if (!m_startThread)
{
QThread::msleep(20);
continue;
}
// 计算 0 ~ m_number的和
int number = m_number;
int sum = 0;
for (int i = 0; i<=number; ++i)
sum += i;
// 打印函数名,线程ID,结果
qDebug() << __FUNCTION__ \
<< " : Current Thread Id is " << QThread::currentThreadId() \
<< ", Result is " << sum;
m_startThread = false;
// 发送信号
emit doFinished(sum);
}
}
// 计算前 0 ~ number的和
void CThread::calcSum(int number)
{
m_number = number;
m_startThread = true;
}
void CThread::onDoFinished(int sum)
{
// 打印函数名,线程ID,结果
qDebug() << __FUNCTION__ \
<< " : Current Thread Id is " << QThread::currentThreadId() \
<< ", Result is " << sum;
}
在 run() 函数,循环执行
调用部分如下:
#include
#include "CThread.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 使用继承QThread的方式开启线程计算前100个数的和
CThread *thread = new CThread;
thread->calcSum(100);
return a.exec();
}
结果如下:
CThread::run : Current Thread Id is 0x68c0 , Result is 5050
CThread::onDoFinished : Current Thread Id is 0x5ce0 , Result is 5050
分析:
关于QThread我个人认知的一点点说明:
(1)使用信号和槽的方式是Qt的推荐方式,有两点好处:
(2)关于线程的等待退出
m_thread.quit();
m_thread.wait();
quit() 函数会退出事件循环,wait() 函数阻塞等待线程退出。
this->requestInterruption();
this->wait();
当使用 isInterruptionRequested() 在 run() 函数作为循环条件时,可以先请求退出,然后再阻塞等待线程的退出。
(3) 线程对象和线程是两个不同的概念。比如上面的例子
CThread *thread = new CThread;
thread 对象就是一个线程对象,该对象的归属是主线程。因此该线程对象的槽函数的执行是在主线程中的;使用函数 moveToThread() 是更改对象的归属线程,因此信号和槽的方式触发函数的执行是在新线程中。值得注意的是,线程中实现槽函数的触发,必须需要执行事件循环即 exec() 函数。
(4)GUI的相关操作只能在主线程中完成。
QWidget等对象的创建和操作必须在主线程中完成,其他的非界面相关的类可以在不同的线程中操作。 moveToThread() 的对象及其父对象必须在同一个线程中。
作者:douzhq
个人主页:http://www.feijiblog.com
文章同步页(文章末尾可下载代码): http://www.feijiblog.com/blog/threadqt