线程是实现一个进程内并发的方式。
线程和进程的区别和联系可以参考:linux中进程,线程,协程
使用线程的场景:
GUI线程和工作线程:
每个程序在启动时都有一个线程。这个线程被称为“主线程”(在Qt应用程序中也称为“GUI线程”)。Qt GUI必须在这个线程中运行。辅助线程通常被称为“工作线程”,因为它用于从主线程中卸载处理工作。
QT中线程的备选方案:
QEventLoop::processEvents()
QTimer
QSocketNotifier QNetworkAccessManager QIODevice::readyRead()
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()
#include
class thread_demo : public QThread
{
Q_OBJECT
public:
thread_demo(QObject *parent = nullptr);
~thread_demo();
protected:
void run() override;
};
#include "thread_demo.h"
#include
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()函数能够在线程中去运行,但是当有多个函数在同一个线程中运行时,就没办法了,至少实现起来很麻烦。
使用信号与槽的方式,也就是把在线程中执行的函数(我们可以称之为线程函数)定义为一个槽函数。
#include
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
#include
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的方法中一个线程可以做很多不同的工作(只要发出任务的对应的信号即可)。
为了避免频繁创建和销毁线程,可以使用QThreadPool复用线程。
要在QThreadPool的某个线程中运行代码,需要重新实现QRunnable::run() 并实例化子类化的QRunnable。使用QThreadPool::start() 将QRunnable放入QThreadPool的运行队列中。当有线程可用时,QRunnable::run() 中的代码将在该线程中执行。
每个 Qt 应用程序都有一个全局线程池,可以通过QThreadPool::globalInstance()进行访问。这个全局线程池会根据 CPU 的核心数量自动维持一个最佳的线程数量。然而,也可以显式地创建和管理一个单独的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
保留一个线程来运行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,即等待最后一个线程退出。
总结:
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创建线程,步骤如下:
与QThread的区别
总结:
QRunnable外界通信的方法:
#include
class demo_runable : public QRunnable
{
public:
void run() override;
};
#include "demo_runable.h"
#include
#include
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);
QtConcurrent模块提供了处理一些常见并行计算模式的高级函数:map、filter 和 reduce。与使用 QThread 和 QRunnable 不同,这些函数从不需要使用互斥锁或信号量等低级线程原语。相反,它们返回一个 QFuture 对象,可用于在函数准备好时检索函数的结果。QFuture 还可用于查询计算进度和暂停 / 恢复 / 取消计算。为了方便起见,QFutureWatcher 支持通过信号和插槽与 QFuture 进行交互。
QtConcurrent的map、filter 和 reduce会自动在所有可用的处理器核心上分配计算任务,因此现在编写的应用程序在以后部署到具有更多核心的系统上时仍能继续扩展。
这个模块还提供了QtConcurrent::run()() 函数,该函数可以在另一个线程中运行任何函数。然而,QtConcurrent::run() 仅支持map、filter 和 reduce可用功能的一个子集。QFuture可用于检索函数的返回值并检查线程是否正在运行。但是,对QtConcurrent::run() 的调用仅使用一个线程,不能暂停 / 恢复 / 取消,也不能查询进度。
#include
#include
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();
QByteArray bytearray = "h,ell,o wo,r,ld";
//调用qt类中的函数
QFuture<QList<QByteArray> > future = QtConcurrent::run(&QByteArray::split,bytearray,',');
qDebug() << "result: " << future.result();
future.waitForFinished();
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;
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()只会返回一个数据
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();
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();
总结
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