QT多线程编程基础
文章目录
- 线程基础
- QT中的多线程技术
- QThread
- 派生QThread类对象的方法(重写Run函数)
- 使用信号与槽方式来实现多线程
- 注意
- QThreadPool和QRunnable
- QThreadPool类
- QRunnable类
- Qt Concurrent
- QtConcurrent基本使用
- 选择合适的方法
- 参考
本文将对QT中的多线程编程进行介绍。
线程基础
线程是实现一个进程内并发的方式。
线程和进程的区别和联系可以参考:linux中进程,线程,协程
使用线程的场景:
- 利用多核处理
- 通过将长时间处理或阻塞调用转移到其他线程来保证主线程的响应
GUI线程和工作线程:
每个程序在启动时都有一个线程。这个线程被称为“主线程”(在Qt应用程序中也称为“GUI线程”)。Qt GUI必须在这个线程中运行。辅助线程通常被称为“工作线程”,因为它用于从主线程中卸载处理工作。
QT中线程的备选方案:
QEventLoop::processEvents()
在耗时计算期间反复调用QEventLoop::processEvents()可防止图形用户界面(GUI)阻塞。然而,此解决方案扩展性不佳,因为根据硬件情况,对 processEvents () 的调用可能过于频繁或不够频繁。QTimer
使用定时器来安排在未来某个时间点执行槽函数有时可以方便地进行后台处理。间隔为 0 的定时器在没有更多事件要处理时会立即超时。QSocketNotifier QNetworkAccessManager QIODevice::readyRead()
这是一种替代方案,可替代拥有一个或多个线程,每个线程在慢速网络连接上进行阻塞式读取。只要对网络数据块的响应计算能够快速执行,这种响应式设计就比线程中的同步等待更好。响应式设计比线程化更不易出错且更节能。在许多情况下,还具有性能优势。
QT中的多线程技术
QThread
QThread是QT多线程的基础,QThread既可以直接实例化,也可以进行子类化。对QThread进行实例化可提供一个并行事件循环,允许在辅助线程中调用QObject的槽函数。对QThread进行子类化可使应用程序在启动其事件循环之前初始化新线程,或者在没有事件循环的情况下运行并行代码。
QThread常用API:
// QThread 类常用 API
// 构造函数
QThread::QThread(QObject *parent = Q_NULLPTR);
// 判断线程中的任务是不是处理完毕了
bool QThread::isFinished() const;
// 判断子线程是不是在执行任务
bool QThread::isRunning() const;
// Qt中的线程可以设置优先级
// 得到当前线程的优先级
Priority QThread::priority() const;
void QThread::setPriority(Priority priority);
优先级:
QThread::IdlePriority --> 最低的优先级
QThread::LowestPriority
QThread::LowPriority
QThread::NormalPriority
QThread::HighPriority
QThread::HighestPriority
QThread::TimeCriticalPriority
QThread::InheritPriority --> 最高的优先级, 默认是这个
// 调用线程退出函数之后, 线程不会马上退出因为当前任务有可能还没有完成, 调回用这个函数是
// 等待任务完成, 然后退出线程, 一般情况下会在 exit() 后边调用这个函数
bool QThread::wait(unsigned long time = ULONG_MAX);
QThread信号槽:
// 退出线程, 停止底层的事件循环
// 退出线程的工作函数
void QThread::exit(int returnCode = 0);
// 和调用 exit() 效果是一样的
// 代用这个函数之后, 再调用 wait() 函数
void QThread::quit();
// 启动子线程
void QThread::start(Priority priority = InheritPriority);
// 线程退出, 可能是会马上终止线程, 一般情况下不使用这个函数
void QThread::terminate();
// 线程中执行的任务完成了, 发出该信号
// 任务函数中的处理逻辑执行完毕了
void QThread::finished();
// 开始工作之前发出这个信号, 一般不使用
void QThread::started();
QThread静态函数:
// Creates a new QThread object that will execute the function f with the arguments args.
QThread *QThread::create(Function &&f, Args &&... args)
// 返回一个指向管理当前执行线程的QThread的指针
QThread *QThread::currentThread();
Qt::HANDLE QThread::currentThreadId()
// 返回可以在系统上运行的理想线程数 == 和当前电脑的 CPU 核心数相同
int QThread::idealThreadCount();
// 线程休眠函数
void QThread::msleep(unsigned long msecs); // 单位: 毫秒
void QThread::sleep(unsigned long secs); // 单位: 秒
void QThread::usleep(unsigned long usecs); // 单位: 微秒
// 放弃当前线程转到另外可执行的线程,有系统决定转到哪个线程。
void QThread::yieldCurrentThread()
派生QThread类对象的方法(重写Run函数)
- 自定义一个自己的类,使其继承自QThread类;
- 在自定义类中覆写QThread类中的虚函数run()。
#include <QThread>
class thread_demo : public QThread
{
Q_OBJECT
public:
thread_demo(QObject *parent = nullptr);
~thread_demo();
protected:
void run() override;
};
#include "thread_demo.h"
#include <QDebug>
thread_demo::thread_demo(QObject *parent):QThread(parent)
{
qInfo() << "create QThread in " << QThread::currentThreadId();
}
thread_demo::~thread_demo()
{
qInfo() << "destroy QThread in " << QThread::currentThreadId();
}
void thread_demo::run()
{
qInfo() << "Qthread run in " << QThread::currentThreadId();
qDebug() << "开始执行线程";
QThread::sleep(10);
qDebug() << "线程结束";
}
子类化QThread的方法,就是重写了QThread中的run()函数,在run()函数中定义了需要的工作。这样的结果是,我们自定义的子线程调用start()函数后,便开始执行run()函数。如果在自定义的线程类中定义相关槽函数,那么这些槽函数不会由子类化的QThread自身事件循环所执行,而是由该子线程的拥有者所在线程(一般都是主线程)来执行。如果你不明白的话,请看,第二个例子中,子类化的线程的槽函数中输出当前线程的ID,而这个ID居然是主线程的ID!!事实的确是如此,子类化的QThread只能执行run()函数中的任务直到run()函数退出,而它的槽函数根本不会被自己的线程执行。
使用信号与槽方式来实现多线程
上面的方法存在一个局限性,只有一个run()函数能够在线程中去运行,但是当有多个函数在同一个线程中运行时,就没办法了,至少实现起来很麻烦。
使用信号与槽的方式,也就是把在线程中执行的函数(我们可以称之为线程函数)定义为一个槽函数。
- 创建一个新的类(demo_worker),让这个类从 QObject 派生,在这个类中添加一个公共的成员函数(slot_startwork),函数体就是我们要子线程中执行的业务逻辑
- 在主线程中创建一个 QThread 对象,这就是子线程的对象
- 在主线程中创建工作的类对象(千万不要指定给创建的对象指定父对象)
- 将 MyWork 对象移动到创建的子线程对象中,需要调用 QObject 类提供的 moveToThread() 方法
- 启动子线程,调用 start(), 这时候线程启动了,但是移动到线程中的对象并没有工作
- 通过信号触发调用 demo_worker 类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的
#include <QObject>
class demo_worker : public QObject
{
Q_OBJECT
public:
explicit demo_worker(QObject *parent = nullptr);
public slots:
void slot_startwork();
signals:
void signal_workdown();
};
#include "demo_worker.h"
#include <QDebug>
#include <QThread>
demo_worker::demo_worker(QObject *parent)
: QObject{parent}
{
qInfo() << "create demo worker in " << QThread::currentThreadId();
}
void demo_worker::slot_startwork()
{
qInfo() << "start demo work " << QThread::currentThreadId();
qDebug() << "开始执行线程";
QThread::sleep(10);
qDebug() << "线程结束";
emit signal_workdown();
}
在主线程中:
qInfo() << "==================";
work_thread = new QThread;
worker = new demo_worker;
connect(this, &demo_app::signal_start_work, worker, &demo_worker::slot_startwork);
connect(worker, &demo_worker::signal_workdown, [](){
qInfo() << "demo worker work down";
});
worker->moveToThread(work_thread);
work_thread->start();
emit signal_start_work();
moveToThread方法,是把我们需要的工作全部封装在一个类中,将每个任务定义为一个的槽函数,再建立触发这些槽的信号,然后把信号和槽连接起来,最后将这个类调用moveToThread方法交给一个QThread对象,再调用QThread的start()函数使其全权处理事件循环。于是,任何时候我们需要让线程执行某个任务,只需要发出对应的信号就可以。其优点是我们可以在一个worker类中定义很多个需要做的工作,然后发出触发的信号线程就可以执行。相比于子类化的QThread只能执行run()函数中的任务,moveToThread的方法中一个线程可以做很多不同的工作(只要发出任务的对应的信号即可)。
注意
- 子线程中不能操作UI:Qt中子线程不能执行任何关于界面的处理,包括消息框的弹出。正确的操作应该是通过信号槽,将一些参数传递给主线程,让主线程(也就是Controller)去处理。
- 自定义的QThread类不能指定父对象
- 注意connect QThread时候的连接类型,它关系着槽函数在哪个线程
QThreadPool和QRunnable
为了避免频繁创建和销毁线程,可以使用QThreadPool复用线程。
要在QThreadPool的某个线程中运行代码,需要重新实现QRunnable::run() 并实例化子类化的QRunnable。使用QThreadPool::start() 将QRunnable放入QThreadPool的运行队列中。当有线程可用时,QRunnable::run() 中的代码将在该线程中执行。
每个 Qt 应用程序都有一个全局线程池,可以通过QThreadPool::globalInstance()进行访问。这个全局线程池会根据 CPU 的核心数量自动维持一个最佳的线程数量。然而,也可以显式地创建和管理一个单独的QThreadPool。
QThreadPool类
主要属性:
1、activeThreadCount: 此属性表示线程池中的活动线程数,通过activeThreadCount() 调用。
2、expiryTimeout: 线程活着的时间。没有设置expiryTimeout毫秒的线程会自动退出,此类线程将根据需要重新启动。默认的expiryTimeout为30000毫秒 (30 秒)。如果expiryTimeout为负, 则新创建的线程将不会过期, 在线程池被销毁之前, 它们将不会退出。通过expiryTimeout()调用,通setExpiryTimeout(int expiryTimeout)设置 。
3、maxThreadCount : int 表示线程池使用的最大线程数。
通过maxThreadCount() 调用,通过setMaxThreadCount(int maxThreadCount) 设置
注意:即使maxThreadCount限制为零或为负数, 线程池也至少有1个线程。
主要成员函数:
QThreadPool *QThreadPool::globalInstance()
返回Qt应用程序全局线程池实例。
void reserveThread()
预约一个线程,这个函数总是会增加活动线程的数量。这意味着通过使用这个函数,activeThreadCount()可以返回一个大于maxThreadCount()的值。
void releaseThread()
释放以前通过调用reserveThread()预约的线程。
如果不先预约一个线程,调用这个函数会临时增加maxThreadCount()。当线程进入休眠等待时,能够允许其他线程继续。
要记得在完成等待时调用reserveThread(),以便线程池可以正确控制activeThreadCount()。
void QThreadPool :: start(QRunnable * runnable,int priority = 0)
在任务数量小于maxThreadCount时,为每个runnable任务预约一个线程。超过maxThreadCount时,将任务放入运行队列中。priority 参数用来设置线程运行优先级。
void QThreadPool::start(std::function<void ()> functionToRun, int priority = 0)
保留一个线程来运行functionToRun
bool tryStart(QRunnable *runnable)
此方法尝试预约一个线程来运行runnable。如果在调用的时候没有线程可用,那么这个函数什么都不做,并返回false。否则,将使用一个可用线程立即运行runnable,并返回此函数true。
void clear()
用于删除在任务队列中,还没有启动的任务。
bool tryTake(QRunnable *runnable)
如果runnable任务还没开始运行,那么从队列中删除此runable任务,此时函数返回true;如果runnable任务已经运行,返回false。
只用来删除runnable->autoDelete() == false的runnable任务,否则可能会删错任务.
bool waitForDone(int msecs = -1)
等待msecs毫秒, 以便所有线程退出并从线程池中移除所有线程。如果删除了所有线程, 则返回true ,否则, 它将返回false。默认等待时间为-1,即等待最后一个线程退出。
总结:
- QThreadPool 类管理 QRunnable /QThread 的集合。
- QThreadPool 管理和回收单独的 QThread 对象,以减少使用线程的程序中的线程创建成本。
- 每个 Qt 应用程序都有一个全局 QThreadPool 对象,可以通过调用 globalInstance() 来访问它。
- 要使用 QThreadPool,需要子类化 QRunnable 并实现 run() 虚函数。然后创建该类的对象并将其传递给 QThreadPool::start()。QThreadPool 默认自动删除 QRunnable。
- QThreadPool 是管理线程的低级类,Qt Concurrent 模块是更高级的方案。
QRunnable类
QRunnable类是所有runable对象的基类。
QRunnable类是一个接口, 用于表示需要执行的任务或代码段, 具体任务在run() 函数内部实现。
可以使用QThreadPool在各个独立的线程中执行代码。如果autoDelete() 返回true (默认值), QThreadPool将自动删除QRunnable 。使用setAutoDelete() 可更改是否自动删除。
主要成员函数:
bool autoDelete() const
获取自动删除是否启用,启用返回true,未启用返回false。
virtual void run() = 0
纯虚函数,在QRunnable子类中实现详细任务处理逻辑。
void setAutoDelete(bool autoDelete)
如果autoDelete为 true, 则启用自动删除。否则自动删除将被禁用。
如果启用了自动删除, QThreadPool将在调用 run () 函数返回后自动删除此runable对象。否则, runable对象所有权不属于线程池,由开发人员管理。
请注意, 必须先设置此标志,(默认构造函数已经将其设置为true),然后才能调用QThreadPool:: start()。在QThreadPool:: start() 之后调用此函数将导致不可预测后果。
QRunnable 类是一个接口,用于表示需要执行的任务或代码段,由重新实现的 run() 函数表示。
一般使用 QThreadPool 在单独的线程中执行代码。要使用QRunnable创建线程,步骤如下:
- 继承QRunnable。和QThread使用一样, 首先需要将你的线程类继承于QRunnable。
- 重写run函数。还是和QThread一样,需要重写run函数,run是一个纯虚函数,必须重写。
- 使用QThreadPool启动线程
与QThread的区别
- 与外界通信方式不同。由于QThread是继承于QObject的,但QRunnable不是,所以在QThread线程中可以直接将线程中执行的结果通过信号的方式发到主程序,而QRunnable线程不能用信号槽,只能通过别的方式。
- 启动线程方式不同。QThread线程可以直接调用start()函数启动,而QRunnable线程需要借助QThreadPool进行启动。
- 资源管理不同。QThread线程对象需要手动去管理删除和释放,而QRunnable则会在QThreadPool调用完成后自动释放。
总结:
- 作为Qt类中少有的基类, QRunnable提供了简洁有效的可运行对象的创建. 用QRunnable来创建独立的运行对象来运行 不涉及界面元素的数据处理过程 非常合适.
- 优点: 创建过程简洁, 使用方便, 配合着自身的autoDelete特性, 有点“招之即来, 挥之即去”的感觉.
- 缺点: 无法实时提供自身的运行状态.
QRunnable外界通信的方法:
- 使用多继承。让我们的自定义线程类同时继承于QRunnable和QObject,这样就可以使用信号和槽,但是多线程使用比较麻烦,特别是继承于自定义的类时,容易出现接口混乱,所以在项目中尽量少用多继承。
- 使用QMetaObject::invokeMethod。
#include <QRunnable>
class demo_runable : public QRunnable
{
public:
void run() override;
};
#include "demo_runable.h"
#include <QDebug>
#include <QThread>
void demo_runable::run() {
qInfo() << "demo runnable in " << QThread::currentThreadId();
qDebug() << "开始执行线程";
QThread::sleep(3);
qDebug() << "线程结束";
}
在主线程中运行:
qInfo() << "++++++++++++++++++";
run_worker = new demo_runable;
QThreadPool::globalInstance()->start(run_worker);
Qt Concurrent
QtConcurrent模块提供了处理一些常见并行计算模式的高级函数:map、filter 和 reduce。与使用 QThread 和 QRunnable 不同,这些函数从不需要使用互斥锁或信号量等低级线程原语。相反,它们返回一个 QFuture 对象,可用于在函数准备好时检索函数的结果。QFuture 还可用于查询计算进度和暂停 / 恢复 / 取消计算。为了方便起见,QFutureWatcher 支持通过信号和插槽与 QFuture 进行交互。
QtConcurrent的map、filter 和 reduce会自动在所有可用的处理器核心上分配计算任务,因此现在编写的应用程序在以后部署到具有更多核心的系统上时仍能继续扩展。
这个模块还提供了QtConcurrent::run()() 函数,该函数可以在另一个线程中运行任何函数。然而,QtConcurrent::run() 仅支持map、filter 和 reduce可用功能的一个子集。QFuture可用于检索函数的返回值并检查线程是否正在运行。但是,对QtConcurrent::run() 的调用仅使用一个线程,不能暂停 / 恢复 / 取消,也不能查询进度。
QtConcurrent基本使用
- QtConcurrent的常用方法有QtConcurrent::run()、QtConcurrent::map()、QtConcurrent::filter()。map和filter还有其衍生的方法,例如还有mapped()、mappedReduced()、filtered()、filteredReduced。下面通过使用这几个方法来了解QtConcurrent的使用方法。
- run方法
run方法是通过另开一个线程来执行用户指定的函数,需要注意的是这个线程是在线程池中获取的,也就是说这个线程是不需要手动释放的,运行完指定的函数线程会自动释放。下面通过几个示例说明一下怎么使用。
- 调用用户自定义的函数
#include <QFuture>
#include <QtConcurrent>
QString hello(QString name,QString name1,QString name2,QString name3)
{
qInfo() << "hello" <<name << "from" <<QThread::currentThread();
for(int i=0; i<3; i++){
QThread::sleep(1);
qInfo() << QString("[%1] i = %2").arg(name).arg(i);
}
return name+name1+name2+name3;
}
QFuture<QString> f1 = QtConcurrent::run(hello,QString("Alice"),QString("Alice"),QString("Alice"),QString("Alice"));
QFuture<QString> f2 = QtConcurrent::run(hello,QString("Bob"),QString("Bob"),QString("Bob"),QString("Bob"));
//QFuture::result()获取单个返回值
qInfo() << "get result";
qDebug() << f1.result();
qDebug() << f2.result();
//等待结束释放
qInfo() << "wait finished";
f1.waitForFinished();
f2.waitForFinished();
- 调用qt类库中的函数
QByteArray bytearray = "h,ell,o wo,r,ld";
//调用qt类中的函数
QFuture<QList<QByteArray> > future = QtConcurrent::run(&QByteArray::split,bytearray,',');
qDebug() << "result: " << future.result();
future.waitForFinished();
- map函数及其衍生函数
- map 函数用于需要更改原容器中数据的使用场景,对容器中的每个项目都调用一次函数,且每次调用都是单独的一个线程。这个没有返回新容器,所以不能通过future获取结果。线程也来自线程池,和run方法类似
QString toUperrMapped(const QString &str){
return str.toUpper();
}
QStringList strWords;
strWords << "Apple" << "Banana" << "cow" << "dog" << "Egg";
//第一个参数是原容器,第二参数是每个项目需要调用的方法
auto future = QtConcurrent::map(strWords,toUpperMap);
future.waitForFinished();
qDebug() << "result: " << strWords;
- mapped方法
mapped 函数用于不改变原容器数据,返回处理新容器结果的使用场景,与map类似,因为返回新容器,所以能通过future获取结果
QString toUperrMappedReduced(const QString &str){
return str.toUpper();
}
QStringList oldStr;
oldStr << "Apple" << "Banana" << "cow" << "dog" << "Egg";
auto future = QtConcurrent::mapped(oldStr,toUperrMapped);
future.waitForFinished();
qDebug() << "result: " << future.results();//results()返回全部数据,这里如果调用result()只会返回一个数据
- mappedReduced()方法
mappedReduced 用于mapped处理后的结果还需要进行处理的使用场景。
QString toUperrMappedReduced(const QString &str){
return str.toUpper();
}
//进一步处理的函数
void reduceFun(QList<QString> &dictionary,const QString &string){
dictionary.push_back(QString("result:")+string);
}
QStringList oldStrReduced;
oldStrReduced << "Apple" << "Banana" << "cow" << "dog" << "Egg";
//最后一个参数是为了保证调用的顺序与输出的顺序一致,否则输出的结果顺序是不确定的,因为每个项目都是单独的一个线程处理,所以输出结果不一样。
auto future = QtConcurrent::mappedReduced(oldStrReduced,toUperrMappedReduced,reduceFun,QtConcurrent::OrderedReduce);
future.waitForFinished();
qDebug() << future.results();
- filter函数及其衍生函数
filter函数与map函数类似,其衍生函数也与map的衍生函数类似,只是函数用于过滤。这里通过一个例子全部展示。
void reduceFun(QList<QString> &dictionary,const QString &string){
dictionary.push_back(QString("result:")+string);
}
bool fiter (QString string){
if(string.length()>3){ //只要长度大于3的字符串
return true;
}else return false;
}
QStringList oldStrfilter;
oldStrfilter << "Apple" << "Banana" << "cow" << "dog" << "Egg";
auto future1 = QtConcurrent::filter(oldStrfilter,fiter);
//filtered函数与mapped函数类似,只是函数用于过滤
auto future2 = QtConcurrent::filtered(oldStrfilter,fiter);
//filteredReduced函数与mappedReduced函数类似,只是函数用于过滤
auto future3 = QtConcurrent::filteredReduced(oldStrfilter,fiter,reduceFun);
future1.waitForFinished();
future2.waitForFinished();
future3.waitForFinished();
qDebug() << oldStrfilter;
qDebug() << future2.results();
qDebug() << future3.results();
总结
- QtConcurrent::run,在线程池内起一个线程来执行一个函数。
- QtConcurrent::map, 用于并行处理一批数据的场景。
- QtConcurrent::filter,一般用于对一批数据的过滤操作。
选择合适的方法
Feature | QThread | QRunnable and QThreadPool | QtConcurrent::run() | Qt Concurrent (Map, Filter, Reduce) | WorkerScript |
---|---|---|---|---|---|
Language | C++ | C++ | C++ | C++ | QML |
Thread priority can be specified | Yes | Yes | |||
Thread can run an event loop | Yes | ||||
Thread can receive data updates through signals | Yes (received by a worker QObject) | Yes (received by WorkerScript) | |||
Thread can be controlled using signals | Yes (received by QThread) | Yes (received by QFutureWatcher) | |||
Thread can be monitored through a QFuture | Partially | Yes | |||
Built-in ability to pause/resume/cancel | Yes |
使用场景:
生命周期 | 开发任务 | 解决方案 |
---|---|---|
一次调用 | 在另一个线程中运行一个函数,函数完成时退出线程 | 1. 编写函数,使用QtConcurrent::run 运行它;2. 派生QRunnable,使用QThreadPool::globalInstance()->start() 运行它; 3. 派生QThread,重新实现QThread::run() ,使用QThread::start() 运行它 |
一次调用 | 需要操作一个容器中所有的项。使用处理器所有可用的核心。一个常见的例子是从图像列表生成缩略图。 | QtConcurrent 提供了map()函你数来将操作应用到容器中的每一个元素,提供了fitler()函数来选择容器元素,以及指定reduce函数作为选项来组合剩余元素。 |
一次调用 | 一个耗时运行的操作需要放入另一个线程。在处理过程中,状态信息需要发送会GUI线程。 | 使用QThread,重新实现run函数并根据需要发送信号。使用信号槽的queued连接方式将信号连接到GUI线程的槽函数。 |
持久运行 | 生存在另一个线程中的对象,根据要求需要执行不同的任务。这意味着工作线程需要双向的通讯。 | 派生一个QObject对象并实现需要的信号和槽,将对象移动到一个运行有事件循环的线程中并通过queued方式连接的信号槽进行通讯。 |
持久运行 | 生存在另一个线程中的对象,执行诸如轮询端口等重复的任务并与GUI线程通讯。 | 同上,但是在工作线程中使用一个定时器来轮询。尽管如此,处理轮询的最好的解决方案是彻底避免它。有时QSocketNotifer是一个替代。 |
Lifetime of thread | Operation | Solution |
---|---|---|
One call | Run a new linear function within another thread, optionally with progress updates during the run. | - Place the function in a reimplementation of QThread::run() and start the QThread. Emit signals to update progress. OR - Place the function in a reimplementation of QRunnable::run() and add the QRunnable to a QThreadPool. Write to a thread-safe variable to update progress. OR - Run the function using QtConcurrent::run(). Write to a thread-safe variable to update progress. |
One call | Run an existing function within another thread and get its return value. | Run the function using QtConcurrent::run(). Have a QFutureWatcher emit the finished() signal when the function has returned, and call QFutureWatcher::result() to get the function’s return value. |
One call | Perform an operation on all items of a container, using all available cores. For example, producing thumbnails from a list of images. | Use Qt Concurrent’s QtConcurrent::filter() function to select container elements, and the QtConcurrent::map() function to apply an operation to each element. To fold the output into a single result, use QtConcurrent::filteredReduced() and QtConcurrent::mappedReduced() instead. |
One call/Permanent | Perfrom a long computation in a pure QML application, and update the GUI when the results are ready. | Place the computation code in a .js script and attach it to a WorkerScript instance. Call WorkerScript.sendMessage() to start the computation in a new thread. Let the script call sendMessage() too, to pass the result back to the GUI thread. Handle the result in onMessage and update the GUI there. |
Permanent | Have an object living in another thread that can perform different tasks upon request and/or can receive new data to work with. | Subclass a QObject to create a worker. Instantiate this worker object and a QThread. Move the worker to the new thread. Send commands or data to the worker object over queued signal-slot connections. |
Permanent | Repeatedly perform an expensive operation in another thread, where the thread does not need to receive any signals or events. | Write the infinite loop directly within a reimplementation of QThread::run(). Start the thread without an event loop. Let the thread emit signals to send data back to the GUI thread. |
参考
Multithreading Technologies in Qt
Threading Basics
Thread Support in Qt
Threads and QObjects