引言
多线程不应该是一个复杂而令人生畏的东西,它应该只是程序员的一个工具,不应该是调用者过多记忆相关概念,而应该是被调用方应该尽可能的简化调用,这也是Qt多线程接口演化的方向。
目前Qt的多线程调用提供了三种方式,一种是子类化QThread后重写run函数,一种是将对象移动到特定线程中,还有一种是通过重写QRunnable的run函数搭配线程池实现,最后一种则是调用高级接口Qt::Concurrent。这三种方式在后面章节会进行详细阐述。
调用方式的简化意味者调用者能够花更多的时间去思考线程的使用是否合理。在通常情况下,我们是将耗时操作推进子线程中执行,防止主线程被阻塞,但在推入子线程之前,应该考虑原有的耗时操作是否有优化控件,能不能将其时间复杂度降低。因为移动到子线程只是解决了界面卡顿的表面问题,需要消耗的资源依然被消耗,如果是无意义的消耗会造成对整个系统的拖累。
除了需要考虑优化函数执行逻辑,还需要考虑的是限制子线程个数,不能无限制推高单个应用的线程数。一个就是线程本事需要消耗资源,另一个是切换线程和保证线程数据安全的资源消耗也是很多的,而且Qt已经提供了很方便的线程池化方案,因此限制线程数是一个有必要且不难达成的良好习惯。
使用方法
重写QThread::run()
/*---------------------WorkerThread-------------------------*/
class WorkerThread : public QThread
{
Q_OBJECT
public:
explicit WorkerThread();
protected:
void run();
signals:
void resultReady(const QString &s);
};
void WorkerThread::run(){
/* ... here is the expensive or blocking operation ... */
}
/*----------------------MainWindow--------------------------*/
void MainWindow::startWorkInAThread()
{
WorkerThread *workerThread = new WorkerThread();
// Release object in workerThread
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
}
以上是通过子类化QThread后重写run函数实现子线程的调用,其中只有run函数内属于子线程,也就是QThread只是一个线程管理类,而其实例本身并不是一个线程,其他的成员和run函数并不在同一线程。
需要注意的是,上述调用线程的方法已经过时,目前官方不推荐使用。
moveToThread
/*--------------------Worker-------------------------*/
class Worker : public QObject
{
Q_OBJECT
public:
Worker();
~Worker();
signals:
void startWorking();
public slots:
void toWork();
};
/*---------------------------Controller----------------------------*/
class Controller : public QObject
{
Q_OBJECT
public:
Controller();
~Controller();
void toWorkDirect();
signals:
void startWorking();
private:
QThread worker_thread_;
Worker *worker_;
};
Worker::Worker()
{
qDebug() << "constructor" << thread()->currentThreadId();
}
Worker::~Worker()
{
qDebug() << "destructor" << thread()->currentThreadId();
}
void Worker::toWork()
{
// to do something
qDebug() << "work function" << thread()->currentThreadId();
}
Controller::Controller() {
worker_ = new Worker;
worker_->moveToThread(&worker_thread_);
connect(this, &Controller::startWorking, worker_, &Worker::toWork);
connect(this, &Controller::startWorking, worker_, [=]{
// lambda函数同样在子线程中
worker_->toWork();
});
connect(&worker_thread_, &QThread::finished, worker_, &QObject::deleteLater);
worker_thread_.start();
}
Controller::~Controller() {
worker_thread_.quit();
worker_thread_.wait();
}
void Controller::toWorkDirect()
{
qDebug() << "work direct function";
worker_->toWork();
}
测试代码
auto button = new QPushButton(this);
connect(button, &QPushButton::clicked, this, [=]{
auto controller = new Controller();
emit controller->startWorking();
controller->toWorkDirect();
QTimer::singleShot(1000, [=]{
controller->deleteLater();
});
});
以上是通过moveToThread将耗时的工作对象推入线程的方法,也是目前官方推荐的QThread使用方法。
上述测试代码中,仅为示例展示线程归属,并不代表实际业务。通过按钮创建controller,点击发送信号startWorking以及直接调用toWorkDirect函数,再通过定时器析构退出线程。通过上图控制台打印可以看到,构造和直接调用函数都是在主线程中,只有通过信号槽连接的函数才会在子线程中,这里可以看出Qt线程的调用方式其实通过事件循环实现的,详细可以参考之前章节。
QRunnable使用
class RunnableObject : public QObject, public QRunnable
{
Q_OBJECT
public:
explicit RunnableObject(QObject *parent = nullptr);
~RunnableObject();
signals:
void workFinished(QString message);
protected:
void run() override {
// to work...
QString temp = QString(__FUNCTION__) + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16);
Q_EMIT workFinished(temp);
};
};
void InitRunnable(QObject *parent)
{
auto runnable_object_ = new RunnableObject(parent);
auto runnable_thread_pool_ = new QThreadPool(parent);
runnable_thread_pool_->start(runnable_object_);
}
首先RunnableObject 继承QRunnable,重写run实现需要放在线程中的函数,再通过线程池的启动函数start(),即可完成在线程池中运行。需要注意的是,默认执行结束后会销毁原来的执行对象,如果希望执行后依然保持工作对象,需要通过函数setAutoDelete(false)去设置。
QtConcurrent使用
auto temp_worker = new Worker();
QtConcurrent::run(temp_worker, &Worker::toWork);
上述代码则是通过高级接口QtConcurrent::run去实现的代码,将temp_worker的toWork函数直接推入子线程中,这样更符合调用者的直觉,相比于前两种调用方式更加简化,也是笔者推荐的线程使用方式。
auto thread_pool = new QThreadPool(this);
thread_pool->setMaxThreadCount(4);
auto temp_worker = new Worker();
QFuture<void> temp_future = QtConcurrent::run(thread_pool, temp_worker, &Worker::toWork);
上述代码是QtConcurrent::run的完整使用方法,包含线程池以及QFuture。如前文所述,Qt的线程池化方案非常简便,设置最大线程数即可。QFuture主要搭配QFutureWatcher使用,用于监控函数的返回值,详细可参考官方文档。
完整代码
示例声明
#ifndef EXAMPLEWIDGET4_HPP
#define EXAMPLEWIDGET4_HPP
#include <QWidget>
class QThread;
class QThreadPool;
class QTextEdit;
class QPushButton;
class ThreadObject;
class WorkerObject;
class RunnableObject;
class ExampleWidget4 : public QWidget
{
Q_OBJECT
public:
explicit ExampleWidget4(QWidget *parent = nullptr);
~ExampleWidget4();
signals:
private:
void InitThreadRun();
void InitMoveToThread();
void InitRunnable();
void InitConcurrent();
private slots:
void toWorkDirect();
private:
QTextEdit *text_edit_;
QPushButton *thread_btn_;
QPushButton *move_thread_btn1_;
QPushButton *move_thread_btn2_;
QPushButton *move_thread_btn3_;
QPushButton *runnable_btn1_;
QPushButton *runnable_btn2_;
QPushButton *concurrent_btn1_;
QPushButton *concurrent_btn2_;
// QThread::run
ThreadObject *thread_object_;
// moveToThread
QThread *worker_thread_;
WorkerObject *worker_object_;
// QRunnable
RunnableObject *runnable_object_;
QThreadPool *runnable_thread_pool_;
// QtConcurrent
WorkerObject *concurrent_worker_;
QThreadPool *concurrent_thread_pool_;
};
#endif // EXAMPLEWIDGET1_HPP
示例实现
#include "example_widget4.hpp"
#include "thread_object.hpp"
#include "worker_object.hpp"
#include "runnable_object.hpp"
#include <QLabel>
#include <QTextEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QtConcurrent>
ExampleWidget4::ExampleWidget4(QWidget *parent)
: QWidget{parent}
{
auto thread_id = new QLabel(this);
thread_id->setText(QString("Main Thread: ") + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16));
text_edit_ = new QTextEdit(this);
text_edit_->setReadOnly(true);
auto clear_btn = new QPushButton("clear", this);
connect(clear_btn, &QPushButton::clicked, this, [=] {
text_edit_->clear();
});
auto main_btn = new QPushButton("Main", this);
connect(main_btn, &QPushButton::clicked, this, [=] {
text_edit_->append(QString("Main Thread: ") + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16));
});
InitThreadRun();
InitMoveToThread();
InitRunnable();
InitConcurrent();
// 布局
auto lable_layout = new QHBoxLayout;
lable_layout->addWidget(thread_id);
lable_layout->addStretch();
lable_layout->addWidget(clear_btn);
auto move_thread_layout = new QHBoxLayout;
move_thread_layout->addWidget(move_thread_btn1_);
move_thread_layout->addWidget(move_thread_btn2_);
move_thread_layout->addWidget(move_thread_btn3_);
auto runnable_layout = new QHBoxLayout;
runnable_layout->addWidget(runnable_btn1_);
runnable_layout->addWidget(runnable_btn2_);
auto concurrent_layout = new QHBoxLayout;
concurrent_layout->addWidget(concurrent_btn1_);
concurrent_layout->addWidget(concurrent_btn2_);
auto button_layout = new QVBoxLayout;
button_layout->addWidget(main_btn);
button_layout->addWidget(thread_btn_);
button_layout->addLayout(move_thread_layout);
button_layout->addLayout(runnable_layout);
button_layout->addLayout(concurrent_layout);
auto main_layout = new QVBoxLayout(this);
main_layout->addLayout(lable_layout);
main_layout->addWidget(text_edit_);
main_layout->addLayout(button_layout);
}
ExampleWidget4::~ExampleWidget4()
{
thread_object_->quit();
thread_object_->wait();
worker_thread_->quit();
worker_thread_->wait();
runnable_thread_pool_->waitForDone();
concurrent_thread_pool_->waitForDone();
}
void ExampleWidget4::InitThreadRun()
{
// QThread重写run函数
thread_object_ = new ThreadObject(this);
connect(thread_object_, &ThreadObject::workFinished, this, [this](QString message) {
text_edit_->append(message);
});
thread_btn_ = new QPushButton("Thread Object Run", this);
connect(thread_btn_, &QPushButton::clicked, thread_object_, [this] {
thread_object_->start();
});
}
void ExampleWidget4::InitMoveToThread()
{
// moveToThread
worker_thread_ = new QThread(this);
worker_object_ = new WorkerObject;// moveToThread的WorkerObject不能有父对象
worker_object_->moveToThread(worker_thread_);
connect(worker_object_, &WorkerObject::workFinished, this, [this](QString message) {
text_edit_->append(message);
});
connect(worker_thread_, &QThread::finished, worker_object_, &QObject::deleteLater);
worker_thread_->start();
move_thread_btn1_ = new QPushButton("Move to Thread Method1", this);
connect(move_thread_btn1_, &QPushButton::clicked, worker_object_, &WorkerObject::toWork);
move_thread_btn2_ = new QPushButton("Move to Thread Method2", this);
connect(move_thread_btn2_, &QPushButton::clicked, worker_object_, [this] {
worker_object_->toWork();
});
move_thread_btn3_ = new QPushButton("Move to Thread Method3", this);
connect(move_thread_btn3_, &QPushButton::clicked, this, &ExampleWidget4::toWorkDirect);
}
void ExampleWidget4::InitRunnable()
{
runnable_object_ = new RunnableObject(this);
runnable_object_->setAutoDelete(false);// autoDelete()为trun,QThreadPool取得的所有权并自动删除它
connect(runnable_object_, &RunnableObject::workFinished, this, [this](QString message) {
text_edit_->append(message);
});
runnable_thread_pool_ = new QThreadPool(this);
runnable_thread_pool_->setMaxThreadCount(1);
runnable_btn1_ = new QPushButton("Runnable1", this);
connect(runnable_btn1_, &QPushButton::clicked, this, [this] {
runnable_thread_pool_->start(runnable_object_);
});
runnable_btn2_ = new QPushButton("Runnable2", this);
connect(runnable_btn2_, &QPushButton::clicked, this, [this] {
auto temp_runnable = new RunnableObject;
connect(temp_runnable, &RunnableObject::workFinished, this, [this](QString message) {
text_edit_->append(message);
});
QThreadPool::globalInstance()->start(temp_runnable);
});
}
void ExampleWidget4::InitConcurrent()
{
// QtConcurrent
concurrent_worker_ = new WorkerObject(this);
connect(concurrent_worker_, &WorkerObject::workFinished, this, [this](QString message) {
text_edit_->append(message);
});
concurrent_thread_pool_ = new QThreadPool(this);
concurrent_thread_pool_->setMaxThreadCount(1);
concurrent_btn1_ = new QPushButton("Concurrent1", this);
connect(concurrent_btn1_, &QPushButton::clicked, this, [this] {
QtConcurrent::run(concurrent_thread_pool_, concurrent_worker_, &WorkerObject::toWork);
});
concurrent_btn2_ = new QPushButton("Concurrent2", this);
connect(concurrent_btn2_, &QPushButton::clicked, this, [this] {
// 默认使用应用程序公共线程池QThreadPool::globalInstance()
QtConcurrent::run(concurrent_worker_, &WorkerObject::toWork);
});
}
void ExampleWidget4::toWorkDirect()
{
worker_object_->toWork();
}
ThreadObject声明
#ifndef THREADOBJECT_HPP
#define THREADOBJECT_HPP
#include <QThread>
class ThreadObject : public QThread
{
Q_OBJECT
public:
explicit ThreadObject(QObject *parent = nullptr);
signals:
void workFinished(QString message);
protected:
void run() override;
};
#endif // THREADOBJECT_HPP
ThreadObject实现
#include "thread_object.hpp"
ThreadObject::ThreadObject(QObject *parent)
: QThread{parent}
{
}
void ThreadObject::run()
{
QString temp = QString(__FUNCTION__) + " 0x" + QString::number((qint64)currentThreadId(), 16);
Q_EMIT workFinished(temp);
}
WorkerObject声明
#ifndef WORKEROBJECT_HPP
#define WORKEROBJECT_HPP
#include <QObject>
#include <QThread>
class WorkerObject : public QObject
{
Q_OBJECT
public:
explicit WorkerObject(QObject *parent = nullptr);
signals:
void workFinished(QString message);
public slots:
void toWork();
};
#endif // WORKEROBJECT_HPP
WorkerObject实现
#include "worker_object.hpp"
#include <QThread>
WorkerObject::WorkerObject(QObject *parent)
: QObject{parent}
{
}
void WorkerObject::toWork()
{
QString temp = QString(__FUNCTION__) + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16);
Q_EMIT workFinished(temp);
}
RunnableObject声明
#ifndef RUNNABLEOBJECT_HPP
#define RUNNABLEOBJECT_HPP
#include <QObject>
#include <QRunnable>
class RunnableObject : public QObject, public QRunnable
{
Q_OBJECT
public:
explicit RunnableObject(QObject *parent = nullptr);
~RunnableObject();
signals:
void workFinished(QString message);
protected:
void run() override;
};
#endif // RUNNABLEOBJECT_HPP
RunnableObject实现
#include "runnable_object.hpp"
#include <QThread>
RunnableObject::RunnableObject(QObject *parent)
: QObject{parent}
{
}
RunnableObject::~RunnableObject()
{
}
void RunnableObject::run()
{
QString temp = QString(__FUNCTION__) + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16);
Q_EMIT workFinished(temp);
}