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中提供了线程同步的支持,比如互斥锁之类的玩意,使用这些方式来访问变量会更加安全。

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

  1. Qt 线程(06):线程和QObject【官翻】

    线程和QObject 前言 QThread继承了QObject. 它发出信号以指示线程已开始执行或完成执行,并且还提供了一些插槽. 更有趣的是,QObjects可以在多个线程中使用,发出调用其他线程中 ...

  2. Python threading Thread多线程的使用方法

    Python threading Thread多线程的使用方法 参考资料:<Python 多线程>http://www.runoob.com/python/python-multithre ...

  3. Android之HandlerThread源码分析和简单使用(主线程和子线程通信、子线程和子线程通信)

    1.先熟悉handler方式实现主线程和子线程互相通信方式,子线程和子线程的通信方式 如果不熟悉或者忘记了,请参考我的这篇博客     Android之用Handler实现主线程和子线程互相通信以及子 ...

  4. 【Qt】Qt多线程开发—实现多线程设计的四种方法

    Qt-使用Qt实现多线程设计的四种方法 文章目录 Qt-使用Qt实现多线程设计的四种方法 一.写在前面 二.[方法一] QThread:带有可选事件循环的底层API 三.[方法二] QThreadPo ...

  5. 【JAVA多线程学习笔记】(1)实现线程的方式 线程生命周期 操作线程的方法

    文章目录 两种方式实现线程 继承Thread类 模拟银行叫号的程序 Runnable接口 代码1:(与swing相结合创建gui程序) Thread类的⼏个常⽤⽅法 线程生命周期 操作线程的方法 代码 ...

  6. Qt多线程-QThreadPool线程池与QRunnable

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Qt多线程-QThreadPool线程池与QRunnable     本文地址:http:// ...

  7. 黑马程序员--线程之间的通信,等待与唤醒机制,线程的终止方式,线程中的其他方法,优先级,toString() 守护线程,GUI图形化界面

    ------<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS ...

  8. Qt 多线程基础及线程使用方式

    文章目录 Qt 多线程操作 2.线程类QThread 3.多线程使用:方式一 4.多线程使用:方式二 5.Qt 线程池的使用 Qt 多线程操作 应用程序在某些情况下需要处理比较复杂的逻辑, 如果只有一 ...

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

    QT 多线程创建方法及应用实例 方法一: (1)创建一个QT应用 (2)创建线程类,继承QThread simplethreadone.h #ifndef SIMPLETHREADONE_H #def ...

最新文章

  1. java读取excel并替换占位符_正则表达式 – 有没有一种简单的方法来替换Excel中的占位符?...
  2. python3安装步骤mac-Mac 安装Python3
  3. 【Android 应用开发】Google 官方 EasyPermissions 权限申请库 ( 最简单用法 | 一行代码搞定权限申请 | 推荐用法 )
  4. MySQL root密码重置 报错:mysqladmin: connect to server at 'localhost' failed的解决方案
  5. table 隔列换色
  6. 选购计算机性能的核心指标,选电脑主要看什么参数呢?买电脑主要看什么参数,有什么技术指标?...
  7. vjue 点击发送邮件如何处理
  8. 获取系统特殊文件夹的路径
  9. CodeForces - 628D Magic Numbers(数位dp)
  10. RT-Thread工程代码框架分析——(1)启动流程
  11. VS2103没有“dirent.h”文件
  12. 【华为云技术分享】HDC.Cloud | 为防止交通事故,95后学生运用“黑科技”这样做!
  13. ios 代码设置控件宽高比_iOS基于代码按比例约束方法进行屏幕适配
  14. 六子棋AI程序---核心讲解
  15. CORS Filter
  16. 2021牛客多校#10 F-Train Wreck(数学,优先队列)
  17. 计算机考研数据库原理知识,数据库原理考研资料题库真题整理
  18. 关于jmeter运行提示没有权限 报错
  19. git文件夹不显示绿勾
  20. C/C++刁钻问题各个击破之细说sizeof .

热门文章

  1. King of Bots项目笔记——后端Bot Running System微服务
  2. html中空格的写法
  3. 电子商务大赛历年获奖作品_武汉商贸职业学院师生在第九届海洋文化创意设计大赛中喜获佳绩! —湖北站—中国教育在线...
  4. win8修改炉石服务器,炉石传说:新的炸服者出现了,这套操作下来就可以成功让炉石服务器成功崩溃...
  5. 关于网络空间资产安全解决方案,你需要了解的
  6. Python的优势、缺点、应用领域介绍
  7. hadoop集群启动命令汇总
  8. 国产当自强,3秒开机,银河麒麟系统有多强?
  9. 用 css before 多个词语加逗号,顿号等
  10. jqgrid 设置冻结列