QT 多线程的实现方法以及GUI线程与其他线程间的通信

article/2025/10/27 17:01:22

GUI线程

Qt应用程序exec后就会生成一个线程,这个线程就是主线程,在GUI程序中也称为GUI线程。主线程也是唯一允许创建QApplication或QCoreAppliation对象,比并且可以对创建的对象调用exec()的线程,从而进入事件循环。

在只有主线程即单线程的情况中,每一个事件的发生都需要进入事件循环进行等待,如有在某一步计算量比较大,则会一直占用CPU不放,导致其它操作无法完成,界面陷入冻结状态
所以,对于计算量大的操作,需要放到一个单独的线程进行计算,然后通过信号槽的方式和GUI线程进行通信。

QT多线程的实现方式

1. 重写QThread的run()

实现方法:
新建一个类,继承QThread,重写虚函数run();

  class WorkerThread : public QThread{Q_OBJECTvoid run() override {QString result;/* ... here is the expensive or blocking operation ... */emit resultReady(result);}signals:void resultReady(const QString &s);};void MyObject::startWorkInAThread(){WorkerThread *workerThread = new WorkerThread(this);connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);workerThread->start();}

优点
可以通过信号槽与外界通信
缺点
每次新建一个线程都需要继承QThread,实现一个新类,使用不太方便。
要自己进行内存的管理(线程的释放和删除);频繁的创建和释放会给系统带来比较大的开销。
适用场景
QThread适用于那些常驻内存的任务

2. QThread的moveToThread**

实现方法
创建一个集成QObject的类(myObject),new 一个QThread,并调用moveToThread(),将创建和的myObject类移动到子线程中,子线程(myObject)通过发发送信号,利用信号槽机制,与主线程进行通信。

 class Worker : public QObject{Q_OBJECTpublic slots:void doWork(const QString &parameter) {QString result;/* ... here is the expensive or blocking operation ... */emit resultReady(result);}signals:void resultReady(const QString &result);};class Controller : public QObject{Q_OBJECTQThread workerThread;public:Controller() {Worker *worker = new Worker;worker->moveToThread(&workerThread);connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);connect(this, &Controller::operate, worker, &Worker::doWork);connect(worker, &Worker::resultReady, this, &Controller::handleResults);workerThread.start();}~Controller() {workerThread.quit();workerThread.wait();}public slots:void handleResults(const QString &);signals:void operate(const QString &);};

优点
1、相对重写QThread::run()函数的方法更加灵活:

moveToThread对比传统子类化Qthread更灵活,仅需要把你想要执行的代码放到槽,movetothread这个object到线程,然后拿一个信号连接到这个槽就可以让这个槽函数在线程里执行。可以说,movetothread给我们编写代码提供了新的思路,当然不是说子类化qthread不好,只是你应该知道还有这种方式去调用线程。

轻量级的函数可以用movethread,多个短小精悍能返回快速的线程函数适用 ,无需创建独立线程类,例如你有20个小函数要在线程内做, 全部扔给一个QThread。而我觉得movetothread和子类化QThread的区别不大,更可能是使用习惯引导。又或者你一开始没使用线程,但是后边发觉这些代码还是放线程比较好,如果用子类化QThread的方法重新设计代码,将会有可能让你把这一段推到重来,这个时候,moveThread的好处就来了,你可以把这段代码的从属着movetothread,把代码移到槽函数,用信号触发它就行了。其它的话movetothread它的效果和子类化QThread的效果是一样的,槽就相当于你的run()函数,你往run()里塞什么代码,就可以往槽里塞什么代码,子类化QThread的线程只可以有一个入口就是run(),而movetothread就有很多触发的入口。

3. QRunnalble的run**

Qrunnable是所有可执行对象的基类。我们可以继承Qrunnable,并重写虚函数
实现方法
1、继承QRunnable。和QThread使用一样, 首先需要将你的线程类继承于QRunnable。
2、重写run函数。还是和QThread一样,需要重写run函数,run是一个纯虚函数,必须重写。
3、使用QThreadPool启动线程

class Runnable:public QRunnable
{//Q_OBJECT   注意了,Qrunnable不是QObject的子类。
public:Runnable();~Runnable();void run();
};Runnable::Runnable():QRunnable()
{}Runnable::~Runnable()
{cout<<"~Runnable()"<<endl;
}void Runnable::run()
{cout<<"Runnable::run()thread :"<<QThread::currentThreadId()<<endl;cout<<"dosomething ...."<<endl;
}
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);cout<<"mainthread :"<<QThread::currentThreadId()<<endl;Runnable runObj;QThreadPool::globalInstance()->start(&runObj);returna.exec();
}

优点:
无需手动释放资源,QThreadPool启动线程执行完成后会自动释放。
缺点:
不能使用信号槽与外界通信。
适用场景:
QRunnable适用于线程任务量比较大,需要频繁创建线程。QRunnable能有效减少内存开销。
和QThread的区别

与外界通信方式不同。由于QThread是继承于QObject的,但QRunnable不是,所以在QThread线程中,可以直接将线程中执行的结果通过信号的方式发到主程序,而QRunnable线程不能用信号槽,只能通过别的方式,等下会介绍。

启动线程方式不同。QThread线程可以直接调用start()函数启动,而QRunnable线程需要借助QThreadPool进行启动。

资源管理不同。QThread线程对象需要手动去管理删除和释放,而QRunnable则会在QThreadPool调用完成后自动释放。

Qt线程之QRunnable的使用详解

4. QtConcurrent的run**

Concurrent是并发的意思,QtConcurrent是一个命名空间,提供了一些高级的 API,使得在编写多线程的时候,无需使用低级线程原语,如读写锁,等待条件或信号。使用QtConcurrent编写的程序会根据可用的处理器内核数自动调整使用的线程数。这意味着今后编写的应用程序将在未来部署在多核系统上时继续扩展。

QtConcurrent::run能够方便快捷的将任务丢到子线程中去执行,无需继承任何类,也不需要重写函数,使用非常简单。详见前面的文章介绍,这里不再赘述。

需要注意的是,由于该线程取自全局线程池QThreadPool,函数不能立马执行,需要等待线程可用时才会运行。
实现方法
1、首先在.pro文件中加上以下内容:QT += concurrent

2、包含头文件#include ,然后就可以使用QtConcurrent了

QFuture fut1 = QtConcurrent::run(func, QString(“Thread 1”)); fut1.waitForFinished();

#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QThread>
#include <QString>
#include <QtConcurrent/QtConcurrentRun>
#include <QTime>
#include<opencv2\opencv.hpp>
#include"XxwImgOp.h"
#ifdef _DEBUG
#pragma comment(lib,".\\XxwImgOpd.lib")
#else
#pragma comment(lib,".\\XxwImgOp.lib")
#endif // _DEBUGusing namespace QtConcurrent;XxwImgOp xxwImgOp;
cv::Mat src = cv::imread("1.bmp", 0);
cv::Mat  dst, dst1, dst2;void hello(cv::Mat src)
{qDebug() << "-----------" << QTime::currentTime()<<"------------------------"<<QThread::currentThreadId();xxwImgOp.fManualThreshold(src, dst, 50, 150);qDebug() <<"************" << QTime::currentTime() <<"**********************"<< QThread::currentThreadId();}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);QFuture<void> f1 = run(hello,  src);QFuture<void> f2 = run(hello, src);//阻塞调用,阻塞主线程直到计算完成f1.waitForFinished();f2.waitForFinished();//阻塞为End的执行顺序qDebug() << "End";return a.exec();
}

特点:

//调用外部函数 QFuture f1 =QtConcurrent::run(func,QString(“aaa”));

//调用类成员函数 QFuture f2 =QtConcurrent::run(this,&MainWindow::myFunc,QString(“bbb”));

要为其指定线程池,可以将线程池的指针作为第一个参数传递进去

向该函数传递参数,需要传递的参数,则跟在函数名之后

可以用run函数的返回值funIr来控制线程。
如: funIr.waitForFinished(); 等待线程结束,实现阻塞。
funIr.isFinished() 判断线程是否结束
funIr, isRunning() 判断线程是否在运行
funIr的类型必须和线程函数的返回值类型相同,可以通过
funIr.result() 取出线程函数的返回值

缺点
不能直接用信号和槽函数来操作线程函数,eg : 当线程函数结束时,不会触发任何信号。

多线程间的通信

方法一

将多线程类对象封装为GUI界面类的类成员
然后在子线程定义信号函数,通过信号槽机制,向界面组件emit发射信号,从而实现间接操作.

方法二

使用QApplication::postEvent()实现向界面发送事件,从而能够封装一个自定义类

方法三

使用Invokes()函数来调用界面组件的信号槽

一般使用该函数(用来调用对方的私有信号或槽):
在这里插入图片描述
该函数的连接方式默认使用的是Qt::AutoConnection
表示如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
比如,当我们想调用一个obj下的compute(QString, int, double)槽函数时:
则只需要写入:

QMetaObject::invokeMethod(obj, "compute",Q_ARG(QString, "sqrt"),                        Q_ARG(int, 42),Q_ARG(double, 9.7));

注意
在QThread线程中不能直接创建QWidget之类的界面组件.
因为在QT中,所有界面组件相关的操作都必须在主线程中(也就是GUI thread)
所以, QThread线程不能直接操作界面组件.

易犯错误

1、子线程中操作UI

Qt创建的子线程中是不能对UI对象进行任何操作的,即QWidget及其派生类对象,这个是我掉的第一个坑。可能是由于考虑到安全性的问题,所以Qt中子线程不能执行任何关于界面的处理,包括消息框的弹出。正确的操作应该是通过信号槽,将一些参数传递给主线程,让主线程(也就是Controller)去处理。

2、信号的参数问题

元对象系统即是提供了Qt类对象之间的信号槽机制的系统。要使用信号槽机制,类必须继承自QObject类,并在私有声明区域声明Q_OBJECT宏。当一个cpp文件中的类声明带有这个宏,就会有一个叫moc工具的玩意创建另一个以moc开头的cpp源文件(在debug目录下),其中包含了为每一个类生成的元对象代码。
在使用connect函数的时候,我们一般会把最后一个参数忽略掉
在这里插入图片描述
我们一般会用到方式是有三种:

* 自动连接(AutoConnection),默认的连接方式。如果信号与槽,也就是发送者与接受者在同一线程,等同于直接连接;如果发送者与接受者处在不同线程,等同于队列连接。
* 

直接连接(DirectConnection)。当信号发射时,槽函数立即直接调用。无论槽函数所属对象在哪个线程,槽函数总在发送者所在线程执行。
*
队列连接(QueuedConnection)。当控制权回到接受者所在线程的事件循环时,槽函数被调用。这时候需要将信号的参数塞到信号队列里。槽函数在接受者所在线程执行。

signals://自定义发送的信号void myThreadSignal(const int, string, string, string, string);

貌似没什么问题,然而实际运行起来槽函数根本就没有被调用,程序没有崩溃,VS也没报错。在查阅了N多博客和资料中才发现,在线程间进行信号槽连接时,参数不能随便写。
为什么呢?我的后四个参数是标准库中的string类型,这不是元对象系统内置的类型,也不是c++的基本类型,系统无法识别,然后就没有进入信号槽队列中了,自然就会出现问题。解决方法有三种,最简单的就是使用Qt的数据类型了

第二种方法就是往元对象系统里注册这个类型。注意,在qRegisterMetaType函数被调用时,这个类型应该要确保已经被完好地定义了。

qRegisterMetaType<MyClass>("MyClass");

方法三是改变信号槽的连接方式,将默认的队列连接方式改为直接连接方式,这样的话信号的参数直接进入槽函数中被使用,槽函数立刻调用,不会进入信号槽队列中。但这种方式官方认为有风险,不建议使用。

connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::DirectConnection)

还有几点需要注意

  • 一定要用信号槽机制,别想着直接调用,你会发现并没有在子线程中执行。
  • 自定义的类不能指定父对象,因为moveToThread函数会将线程对象指定为自定义的类的父对象,当自定义的类对象已经有了父对象,就会报错。
  • 当一个变量需要在多个线程间进行访问时,最好加上voliate关键字,以免读取到的是旧的值。当然,Qt中提供了线程同步的支持,比如互斥锁之类的玩意,使用这些方式来访问变量会更加安全。

http://chatgpt.dhexx.cn/article/cAMkrNaH.shtml

相关文章

QT多线程接收串口数据

** QT多线程接收串口数据 ** 1.前言 QT多线程的使用&#xff0c;和绝大数人一样&#xff0c;犯了错误&#xff08;请查阅Qt开发人员( Bradley T. Hughes)Blog中的文章 you are-doing-it-wrong介绍&#xff09;。为了解决问题&#xff0c;网上查阅学习了几十篇文章&#xff0c…

Qt多线程互斥

目录 一、多线程与临界资源的依赖 现象分析 二、互斥和解决方法 三、QMutex的主要成员函数和使用 四、示例代码 五、小结 一、多线程与临界资源的依赖 除了上一节所说的&#xff0c;多线程在代码执行的时序上会有依赖&#xff0c;那么其他地方是否还有所依赖呢&#xff…

QT 多线程创建方法及应用实例

QT 多线程创建方法及应用实例 方法一&#xff1a; &#xff08;1&#xff09;创建一个QT应用 &#xff08;2&#xff09;创建线程类&#xff0c;继承QThread simplethreadone.h #ifndef SIMPLETHREADONE_H #define SIMPLETHREADONE_H #include <QThread>class SimpleThr…

Qt 多线程专栏

主线程就是进程&#xff0c;进程代表4个G的资源空间&#xff0c;主线程退出就意味着进程退出&#xff0c;在Qt中&#xff0c;主线程不能是子线程的父对象&#xff0c;因此必须在线程的析构函数中去delete线程对象。 只要定时器启动&#xff0c;自动触发timeout()信号 类A继承了…

Qt多线程编程

由于疫情原因被封控在家&#xff0c;相信大家也有跟我类似的情况&#xff0c;在学习知识提升和充实自己&#xff0c;下面我将这几天在学习的Qt多线程编程做个简单的总结&#xff0c;随此笔记的还有几个简单的例子&#xff0c;以便后续开发过程中回忆和参考。 Qt的多线程开发&am…

Qt多线程

Qt多线程有两种方法&#xff1a; 1、定义一个QThread的派生类&#xff0c;重写run函数&#xff0c;run函数中的程序就是新线程中要执行的内容。在主线程中实例化该类&#xff0c;调用start函数&#xff0c;就可以实现多线程。 2、定义一个QObject的派生类&#xff0c;把要在新线…

Qt创建多线程的两种方法

来源&#xff1a;https://github.com/czyt1988/czyBlog/tree/master/tech/QtThread 1.摘要 Qt有两种多线程的方法&#xff0c;其中一种是继承QThread的run函数&#xff0c;另外一种是把一个继承于QObject的类转移到一个Thread里。 Qt4.8之前都是使用继承QThread的run这种方法…

Qt----多线程

文章目录 多线程一、继承QThread的线程1. 应用实例2. 程序运行效果 二、继承QObject的线程1. 应用实例2. 程序运行效果 多线程 我们写的一个应用程序&#xff0c;应用程序跑起来后一般情况下只有一个线程&#xff0c;但是可能也有特殊情况。比如我们前面章节写的例程都跑起来后…

Qt5教程(九):Qt多线程

目录&#xff1a; 一、创建工程 二、QThread 源码一览 三、QThread相关方法介绍 四、创建线程 一、创建工程 先创建一个工程吧, 具体步骤前面讲过很多次了&#xff0c; 就不再细说了。 然后在Header文件夹下创建添加一个头文件, 右键Headers -> Add New... -> C -&g…

Qt 多线程的几种实现方式

Qt多线程的实现方式有&#xff1a; 1. 继承QThread类&#xff0c;重写run()方法 2. 使用moveToThread将一个继承QObject的子类移至线程&#xff0c;内部槽函数均在线程中执行 3. 使用QThreadPool,搭配QRunnable&#xff08;线程池&#xff09; 4. 使用QtConcurrent&#xf…

QT中的多线程

目录 1、QThread介绍 1.2、继承Qthread类 1.2.1、得到线程id 1.2.2、让线程一直执行 1.2.3、线程退出 1.2.4、​​​​​​​connect的第五个参数 1.2.5、线程锁 QMutex ​​​​​​​ 1、QThread介绍 QThread类提供了一个与平台无关的管理线程的方法。一个QThread对…

faild to create process解决办法

win7下运行pip时报faild to create process的解决办法&#xff1a; 1.找到python.exe文件&#xff0c;复制其文件路径。如下图&#xff0c;python.exe路径为D:\interpreter\python.exe。 2.找到pip-script.py文件&#xff0c;打开。将步骤1中复制的路径粘贴在第一行&#xff0…

CreateProcess error=2, 系统找不到指定的文件 解决方法

CreateProcess error2, 系统找不到指定的文件 解决方法 一般这是由于ndk缺少文件引起的 解决方法1&#xff1a; 在项目根目录下的local.properties文件中加cmd后缀 方法二下载16b的版本替换原来的ndk-bundle目录&#xff0c;默认在C:\Users\用户名\AppData\Local\Android\S…

SQL SERVER 2008 执行xp_cmdshell的过程中出错,调用createprocess失败,错误代码 5 解决方案

1、进入 控制面板----管理工具------本地安全策略 点击 本地策略--------安全选项 把“网络安全:LAN管理器身份验证级别”双击打开&#xff0c;选择“发送 LM 和 NTLM 响应”&#xff08;原来是“没有定义”&#xff09;。确认后&#xff0c;重新启动服务器&#xff0c;该问题…

runnerw.exe: CreateProcess failed with error 5:

在idea配置Git时遇到这个问题&#xff1a; 后来发现是我路径设置错了&#xff1a; 解决方案 修改设置里的路径即可&#xff08;Setings-Version Control-Git&#xff09;

Qt creater出现“启动程序失败,路径或者权限错误”或“The process could not be started!”解决方法

Qt creater出现“启动程序失败&#xff0c;路径或者权限错误”或“The process could not be started&#xff01;”解决方法 错误示例 远古版本的QTCreator在编译程序时没问题&#xff0c;在运行程序时会提示“The process could not be started&#xff01;" 较新版本…

failed to create process.

由于python-2.7是先出来&#xff0c;但是官方只更新到2020年1月1日&#xff1b;而python-3.6与python-2.7有一定的差别&#xff0c;会同时在电脑上安装这两个版本的python&#xff0c;为了能更好的调用python不同的版本&#xff0c;会设定一个执行时使用python2.exe&#xff0c…

CreateProcessAsUser

该CreateProcessAsUser函数创建一个新的进程及其主线程。新进程然后执行指定的可执行文件。该CreateProcessAsUser功能类似的CreateProcess函数&#xff0c;除了新进程运行在由hToken参数表示的用户的安全上下文中。默认情况下&#xff0c;新进程是非交互式的&#xff0c;即它运…

生成了文件却还是报错 Error:CreateProcess failed

想起来&#xff0c;以前用keil编译器的时候&#xff0c;也出现过这样的问题&#xff1a; 第一眼都是看到了“1 Error(s)”&#xff0c;就下意识认为自己程序出错了&#xff0c;找了半天没找到。 后来多看了一眼&#xff0c;发现完全这个错误其实可以完全不用理会&#xff0c;因…

【Error】Error running process: CreateProcess failed. Code 2

重新安装了pycham发现在pycharm里打不开控制台窗口了 解决办法&#xff1a; 在file -->setting --> Tools --> Terminal里 把Shell path 从 powershell.exe 改为 cmd.exe