QT多线程编程基础

文章目录

    • 线程基础
    • QT中的多线程技术
      • QThread
        • 派生QThread类对象的方法(重写Run函数)
        • 使用信号与槽方式来实现多线程
        • 注意
      • QThreadPool和QRunnable
        • QThreadPool类
        • QRunnable类
      • Qt Concurrent
        • QtConcurrent基本使用
      • 选择合适的方法
    • 参考

本文将对QT中的多线程编程进行介绍。

线程基础

线程是实现一个进程内并发的方式。
线程和进程的区别和联系可以参考:linux中进程,线程,协程

使用线程的场景:

  1. 利用多核处理
  2. 通过将长时间处理或阻塞调用转移到其他线程来保证主线程的响应

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::LowestPriorityQThread::LowPriorityQThread::NormalPriorityQThread::HighPriorityQThread::HighestPriorityQThread::TimeCriticalPriorityQThread::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函数)
  1. 自定义一个自己的类,使其继承自QThread类;
  2. 在自定义类中覆写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()函数能够在线程中去运行,但是当有多个函数在同一个线程中运行时,就没办法了,至少实现起来很麻烦。
使用信号与槽的方式,也就是把在线程中执行的函数(我们可以称之为线程函数)定义为一个槽函数。

  1. 创建一个新的类(demo_worker),让这个类从 QObject 派生,在这个类中添加一个公共的成员函数(slot_startwork),函数体就是我们要子线程中执行的业务逻辑
  2. 在主线程中创建一个 QThread 对象,这就是子线程的对象
  3. 在主线程中创建工作的类对象(千万不要指定给创建的对象指定父对象)
  4. 将 MyWork 对象移动到创建的子线程对象中,需要调用 QObject 类提供的 moveToThread() 方法
  5. 启动子线程,调用 start(), 这时候线程启动了,但是移动到线程中的对象并没有工作
  6. 通过信号触发调用 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外界通信的方法:

  1. 使用多继承。让我们的自定义线程类同时继承于QRunnable和QObject,这样就可以使用信号和槽,但是多线程使用比较麻烦,特别是继承于自定义的类时,容易出现接口混乱,所以在项目中尽量少用多继承。
  2. 使用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基本使用
  1. QtConcurrent的常用方法有QtConcurrent::run()、QtConcurrent::map()、QtConcurrent::filter()。map和filter还有其衍生的方法,例如还有mapped()、mappedReduced()、filtered()、filteredReduced。下面通过使用这几个方法来了解QtConcurrent的使用方法。
  2. 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();
  1. 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();
  1. 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,一般用于对一批数据的过滤操作。

选择合适的方法

FeatureQThreadQRunnable and QThreadPoolQtConcurrent::run()Qt Concurrent (Map, Filter, Reduce)WorkerScript
LanguageC++C++C++C++QML
Thread priority can be specifiedYesYes
Thread can run an event loopYes
Thread can receive data updates through signalsYes (received by a worker QObject)Yes (received by WorkerScript)
Thread can be controlled using signalsYes (received by QThread)Yes (received by QFutureWatcher)
Thread can be monitored through a QFuturePartiallyYes
Built-in ability to pause/resume/cancelYes

使用场景:

生命周期开发任务解决方案
一次调用在另一个线程中运行一个函数,函数完成时退出线程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 threadOperationSolution
One callRun 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 callRun 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 callPerform 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/PermanentPerfrom 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.
PermanentHave 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.
PermanentRepeatedly 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/21281.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用DeepSeek零基础预测《哪吒之魔童闹海》票房——从数据爬取到模型实战

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录 **一、为什么要预测票房&#xff1f;****二、准备工作****三、实战步骤详解****Step 1&#xff1a;数据爬取与清洗&am…

高并发下秒杀系统的设计

文章目录 1 业界通用做法1.1 压力分摊1.2 RedisMySQL1.3 Inventory Hint1.4 压力分摊RedisMQ 2 Redis MQ 解决高并发下的秒杀场景2.1 Redis库存预扣减2.1.1 lua脚本执行流程&#xff1a;2.1.2 Lua脚本主要做了几件事&#xff1a; 2.2 MySQL库存扣减2.3 记录操作流水的原因 3 I…

双重差分学习笔记

双重差分适用的研究场景&#xff1a; 研究某项政策或者冲击造成的影响 例如&#xff0c;某某小学在2024.12.12日颁布了小红花激励措施&#xff0c;我们要研究这项措施对学生成绩的影响&#xff0c;此时&#xff0c;就可以使用双重差分模型。 双重差分适用的数据类型&#xf…

深入理解 C++17 中的 std::atomic<T>::is_always_lock_free

文章目录 原子操作与锁无关性&#xff08;Lock-Free&#xff09;锁无关性&#xff08;Lock-Free&#xff09;无锁&#xff08;Lock-Free&#xff09;与无阻塞&#xff08;Wait-Free&#xff09; std::atomic<T>::is_always_lock_free 是什么&#xff1f;truefalse与 is_l…

VSCode 中 Git 添加了多个远端,如何设置默认远端

VSCode 中 Git 添加了多个远端&#xff0c;如何设置默认远端 查看分支&#xff1a;设置默认远端手动指定远端 查看分支&#xff1a; * 表示当前默认远端 git branch -vv* master a1b2c3d [origin/main] Fix typo dev d4e5f6g [upstream/dev] Add feature设置默认远端 将本…

一文讲清 AIO BIO NIO的区别

引言 在 Java 编程中&#xff0c;BIO&#xff08;Blocking I/O&#xff09;、NIO&#xff08;Non-blocking I/O&#xff09;和 AIO&#xff08;Asynchronous I/O&#xff09;是三种不同的 I/O 模型&#xff0c;它们在处理输入输出操作时有着不同的机制和特点&#xff0c;但是市…

使用(xshell+xftp)将前端项目部署到服务器

一.以vue项目为例 将项目打包生成dist文件 二.下载载安装xshell和xftp 下载地址&#xff1a;家庭/学校免费 - NetSarang Website 三.连接服务器 在xshell新建会话&#xff08;需要用到服务器、用户名、密码、端口号&#xff09;正确输入后连接到服务器 使用命令连接&#x…

硬件岗位是否适合你?

在当今科技飞速发展的时代,硬件行业作为技术创新的基石,始终扮演着至关重要的角色。无论是智能手机、自动驾驶汽车,还是人工智能服务器,硬件都是这些技术的核心支撑。然而,硬件岗位是否适合你?作为一名硬件专家,我将从多个角度为你分析,帮助你判断自己是否适合从事硬件…

Linux基本指令(二)

文章目录 基本指令echocat&#xff08;输入重定向&#xff09;history日志moretail和headmv&#xff08;重要&#xff09;时间相关的指令查找的命令 知识点Linux下一切皆文件为什么计算机关机了&#xff0c;开机后还能准确地记录时间呢&#xff1f; 基本指令 echo 1. echo&…

【Blender】二、建模篇--05,阵列修改器与晶格形变

阵列修改器是bender里面一个比较常用的修改器,所以我们单独开口来讲,我们会先从几片树叶出发,然后我们用阵列修改器把这几片树叶变成这样的造型和这样的造型。这两个造型分别就代表着阵列修改器最常用的两种偏移方法,我们现在就开始我们先来做几个树叶。 1.树叶建模 首先…

fpga助教面试题

第一题 module sfp_pwm( input wire clk, //clk is 200M input wire rst_n, input wire clk_10M_i, input wire PPS_i, output reg pwm ) reg [6:0] cunt ;always (posedge clk ) beginif(!rst_n)cunt<0;else if(cunt19) //200M是10M的20倍cunt<0;elsecunt<cunt1;…

SpringAI系列 - ToolCalling篇(二) - 如何设置应用侧工具参数ToolContext(有坑)

目录 一、引言二、集成ToolContext示例步骤1: 在`@Tool`标注的工具方法中集成`ToolConext`参数步骤2:`ChatClient`运行时动态设置`ToolContext`参数三、填坑一、引言 在使用AI大模型的工具调用机制时,工具参数都是由大模型解析用户输入上下文获取的,由大模型提供参数给本地…

Jest单元测试

由于格式和图片解析问题&#xff0c;可前往 阅读原文 前端自动化测试在提高代码质量、减少错误、提高团队协作和加速交付流程方面发挥着重要作用。它是现代软件开发中不可或缺的一部分&#xff0c;可以帮助开发团队构建可靠、高质量的应用程序 单元测试&#xff08;Unit Testi…

pyside6学习专栏(二):程序图像资源的加载方式

pyside6中的QLabel控件可以加载图像和gif动画&#xff0c;可以直接从外部文件加载&#xff0c;也可以从QRC类型的文件(实际是一脚本文件)经编绎生成对应的资源.PY模块文件(就是将qrc文本中指定的资源文件的16制内容写入.py文件)来使用&#xff0c;本文对两种方式作了一简单的示…

Nginx--日志(介绍、配置、日志轮转)

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 一、Nginx日志介绍 nginx 有一个非常灵活的日志记录模式&#xff0c;每个级别的配置可以有各自独立的访问日志, 所需日志模块 ngx_http_log_module 的…

cs106x-lecture12(Autumn 2017)-SPL实现

打卡cs106x(Autumn 2017)-lecture12 (以下皆使用SPL实现&#xff0c;非STL库&#xff0c;后续课程结束会使用STL实现) travel Write a recursive function named travel that accepts integers x and y as parameters and uses recursive backtracking to print all solution…

了解随机振动疲劳分析中 Ansys nCode DesignLife 的平均应力校正

概括 在本篇博文中&#xff0c;我们将探讨 Ansys nCode 在分析随机振动引起的疲劳方面的重要性。我们将了解 nCode 如何帮助校正平均应力并预测受随机振动影响的结构的寿命和耐久性。 什么是疲劳寿命以及了解平均应力对疲劳寿命的影响 疲劳寿命是指结构在重复载荷作用下发生…

ubuntu20.04重启后不显示共享文件夹

ubuntu20.04重启后不显示共享文件夹 主要参见这两篇博客 Ubuntu重启后不显示共享文件夹_ubuntu 20.04 共享目录无法使用-CSDN博客 ubuntu22.04 配置共享文件夹 找不到/mnt/hgfs_ubuntu安装tools 后mnt文件夹在哪-CSDN博客 重启Ubuntu20.04后&#xff0c;发现共享文件夹进不去…

Rust编程语言入门教程 (六)变量与可变性

Rust 系列 &#x1f380;Rust编程语言入门教程&#xff08;一&#xff09;安装Rust&#x1f6aa; &#x1f380;Rust编程语言入门教程&#xff08;二&#xff09;hello_world&#x1f6aa; &#x1f380;Rust编程语言入门教程&#xff08;三&#xff09; Hello Cargo&#x1f…

nvm安装、管理node多版本以及配置环境变量【保姆级教程】

引言 不同的项目运行时可能需要不同的node版本才可以运行&#xff0c;由于来回进行卸载不同版本的node比较麻烦&#xff1b;所以需要使用node工程多版本管理。 本人在配置时&#xff0c;通过网络搜索教程&#xff0c;由于文章时间过老&#xff0c;或者文章的互相拷贝导致配置时…