QThread Qt

函数moveToThread()

函数原型:

void QObject::moveToThread(QThread *targetThread);
该函数用来改变对象的线程依附性,及该对象所属的线程,改变线程后,该对象的事件循环将在目标线程继续运行(对象收到的事件,发送的事件,都将通过目标线程的QThread::exec()事件循环处理),一定要注意,该对象的子对象所属线程也会随次线程改变,另外,调用次函数之前,不能为此对象指定父对象,详细参见Qt Assistant。

验证:

子类化QThread为ProcessThread类,并在其构造函数中new一个MyProcess类对象processMgmt,test对象是processMgmt的子对象,调用moveToThread()函数之后输出所属线程对象的指针:

ProcessThread::ProcessThread(QObject *parent) : QThread(parent)
{this->processMgmt = new MyProcess;processMgmt->moveToThread(this);qDebug() << "processMgmt's thread pointer is " << processMgmt->thread();qDebug() << "processMgmt->test's thread pointer is " << processMgmt->test->thread();qDebug() << "qApp's thread pointer is " << QApplication::instance()->thread();this->start();
}

运行结果为:

processMgmt's thread pointer is  ProcessThread(0x589071f7d8)
processMgmt->test's thread pointer is  ProcessThread(0x589071f7d8)
qApp's thread pointer is  QThread(0x1b40c2dc150)

可以明显看出子对象和父对象的线程为一个线程,并且和主线程不是一个线程,括号中的值是所属线程对象的指针。

另外,请注意一点,如果类中存在成员对象(其他类的对象,但是是该类的成员,上述test对象就是processMgmt的成员对象,但同时test是processMgmt的子对象),且该成员对象不是此类的子对象,则该成员对象的所属线程不变,为了方便说明,我们还是给出test和processMgmt对象所对应的类定义:

myprocess.h文件:

#ifndef MYPROCESS_H
#define MYPROCESS_H#include <QProcess>class Test :public QObject
{Q_OBJECT
public:Test(QObject * parent = NULL):QObject(parent){}
};class MyProcess : public QProcess
{Q_OBJECT
public:explicit MyProcess(QObject *parent = 0);Test test;
signals:public slots:
};#endif // MYPROCESS_H

myprocess.cpp文件:

#include "myprocess.h"MyProcess::MyProcess(QObject *parent) : QProcess(parent), test(this)
{
}

上述两文件对应第一种情况,test是processMgmt对象的子对象,只需要将myprocess.cpp文件稍作修改,就可验证第二种情况:

#include "myprocess.h"MyProcess::MyProcess(QObject *parent) : QProcess(parent)
{
}

运行结果为:

processMgmt's thread pointer is  ProcessThread(0x3f0ccff938)
processMgmt->test's thread pointer is  QThread(0x15c3b1c08d0)
qApp's thread pointer is  QThread(0x15c3b1c08d0)

可以看出,当类成员对象不是子对象时,类成员对象的所属线程不会随它所属的类的线程的改变而改变。

线程间信号的传递

调用另一个线程的函数(一般为另一个线程上的对象的成员函数),可以采用信号与槽,QMetaObject::invoke(),connect连接方式采用非阻塞连接时(Qt::QueuedConnection),信号与槽函数参数的传递尽量不要传递引用,或指针,  (因为非阻塞也就是两线程无任何同步机制,同时操作同一块无线程安全保护的内存区域是很危险的);尽量采用值传递(QMetaObject::invoke在非阻塞调用时也要如此),参数为自己定义的对象时,还要使用宏qRegisterMetaType()进行类型注册,详见:http://blog.csdn.net/faith_yu/article/details/53283941;下面我贴出线程间无任何安全措施传递引用的代码:

daemon.h文件:

class Daemon : public QObject
{Q_OBJECT
public:explicit Daemon(const QStringList &, QObject *parent = 0);~Daemon();void setProStatus(bool status) { isRunning = status;}bool proStatus() { return isRunning;}signals:void restartFailed(QString&);void noPythonEnv();void closeFailed(QString&);void readyRead(QString&, QString&);void startRun(QString &);void terminateRun(QString &);public slots:void startAllPro();void closeAllPro();void setProPaths(const QStringList );
private slots:void output();private:QMap<QProcess *, QString> proMap;QList<QProcess *> proList;time_t * preTime;int * crashTimes;void proExitHandler(/*int exitCode, QProcess::ExitStatus exitStatus*/);bool isRunning = false;
};

daemon类的用途是用来做守护进程,监视其它进程是否crash,如果crash则重新启动它,daemon类对象一般单独放在一个线程,但其所有的槽函数都不是线程安全的,使用时注意调用方式,我们先不关心它的功能,首先注意到它的signals参数全为QString& 引用类型,这注定daemon是一个失败的类,MainWindow类信号连接方式如下:

    connect(daemonThread->daemon, &Daemon::restartFailed, this, &MainWindow::daemonRstFailedHandler);//, Qt::BlockingQueuedConnection);connect(daemonThread->daemon, &Daemon::noPythonEnv, this, &MainWindow::daeNoPyHandler);//, Qt::BlockingQueuedConnection);connect(daemonThread->daemon, &Daemon::readyRead, this, &MainWindow::outToTE);//, Qt::BlockingQueuedConnection);

注意,connect都是以非阻塞的方式连接(使用阻塞的方式连接可能允许线程间的引用参数传递,因为主线程操作子线程内存区域时,子线程一直处于阻塞状态,但是这样就留下了隐患)

MainWindow槽函数对应如下:

void MainWindow::outToTE(QString & serPath, QString & message)
{QPlainTextEdit * temp = mapComb[mapPaths[serPath]]->text;QTextCursor textCursor = temp->textCursor();textCursor.movePosition(QTextCursor::End);textCursor.insertText(message);QScrollBar * scrollBar = temp->verticalScrollBar();if(scrollBar){scrollBar->setSliderPosition(scrollBar->maximum());}
}void MainWindow::daeNoPyHandler()
{myMessageBox(this, QMessageBox::Warning, QStringLiteral("警告") ,QStringLiteral("无法启动python"),\QStringLiteral("请检查系统是否安装python并将python放入系统环境变量"));qApp->quit();
}void MainWindow::daemonRstFailedHandler(QString & serverPath)
{myMessageBox(this, QMessageBox::Warning, QStringLiteral("警告"), QStringLiteral("已尝试多次重启服务,均已失败,请确认服务路径正确并重启程序"),\QStringLiteral("出错服务为") + serverPath);qApp->quit();
}

槽函数中对应的参数也是引用类型,这会导致很严重的后果,但是还好我在遭遇真正的错误之前,被另一个小错误救了一命,运行时并没有出现非法访问内存的情况,而是信号发送失败:

QObject::connect: Cannot queue arguments of type 'QString &' (Make sure 'QString &' is registed using qRegisterMetaType().) 

解决这个问题可以参考上一点所讲,调用qRegisterMetaType()对QString&类型进行注册(qt将引用类型当做自定义类型),但是如果你没有意识到掩藏在这个小错误之后的重大问题,那么程序将会真正崩溃,真正的解决办法只需将槽函数定义和声明以及信号的声明参数类型重QString&改为QString即可。

线程死锁

两个线程之间的信号与槽传递一定要避免出现死锁,着用情况很容易出现,例如Qt::BlockingQueuedConnection下,发送者和接受者在同一个线程时(应该禁用这种方式),会导致线程死锁,另外如果接收信号的对象所在线程没有事件循环,也会导致发送信号对象所在线程死锁,因为发送的信号无法被事件循环处理,而发送信号的线程却在等待处理结果,所以死锁;

我曾经遇见过一种很隐蔽的情况,子线程发送异常信号给主线程(阻塞的方式),主线程收到信号退出程序,但是退出前delte 自定义线程类DaemonThread(它实际属于主线程),MyThread的析构函数中调用了this->quit();this->wati();这两个函数实际作用是向子线程发送退出信号,并等待退出,但此时子线程事件循环正在阻塞,因为它以阻塞方式发送的异常信号给主线程,这就导致了死锁,所以有些死锁的情况是难以发现的,所以使用时要格外小心,上述死锁过程过程代码:

与子线程restartFailed信号对应的主线程的处理函数:

void MainWindow::daemonRstFailedHandler(QString serverPath)
{myMessageBox(this, QMessageBox::Warning, QStringLiteral("警告"), QStringLiteral("已尝试多次重启服务,均已失败,请确认服务路径正确并重启程序"),\QStringLiteral("出错服务为") + serverPath);delete daemonThread;//导致死锁qApp->quit();
}

DaemonThread类的析构函数:

DaemonThread::~DaemonThread()
{daemon->deleteLater();this->quit();this->wait();
}

信号连接:

connect(daemonThread->daemon, &Daemon::restartFailed, this, &MainWindow::daemonRstFailedHandler, \Qt::BlockingQueuedConnection);

使用QThread类的两种常用形式

1.使用moveToThread()函数

这种情况主要用于在子线程处理需要事件循环的类对象,此时一般不再重写QThread::run()函数,QThread::run()的原始定义中只有一条语句:

void QThread::run()
{(void) exec();
}

它只是打开了线程的事件循环,这种情况下,我们可以子类化QThread类,也可以直接实例化QThread类型的对象,然后将对象作为moveToThread()函数的参数;

a.直接使用QThread实例化对象

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 &);
};

这是一个来自《Qt Creator快速入门》的例子,Worker为笔者自定义的类,以第一个connect语句为例,其实现的效果:当依附于主线程的对象workerThread发出信号finished时worker的槽函数deleteLater()被异步调用,并在子线程上执行,这里要明确一点:workerThread对象是依附于主线程的,只是对象的run()函数运行于子线程,子线程上的run()函数类似于主线程的main()函数;

b.另外也可以子类化QThread对象,但是同样不重写run()函数:

这里使用自己实现的用于守护Python脚本运行的Daemon类作为举例,在讲述线程间的信号传递时用到过bad版Daemon类。注意,Daemon类的除 构造函数的所有函数都不是线程安全的,切勿直接在另一线程直接调用(可以尝试使用QMetaObject::invoke()函数),在另一篇讲述QProcess类的博文中将贴出Daemon的实现,并具体讲述:

子类化QThread类为DaemonThread,daemonthread.文件:

#ifndef DAEMONTHREAD_H
#define DAEMONTHREAD_H#include <QThread>class Daemon;class DaemonThread : public QThread
{Q_OBJECT
public:explicit DaemonThread(QObject *parent = 0);~DaemonThread();Daemon * daemon;signals:public slots:
};#endif // DAEMONTHREAD_H

daemon.cpp

#include "daemonthread.h"
#include <QThread>
#include "daemon.h"DaemonThread::DaemonThread(QObject *parent) : QThread(parent)
{daemon = new Daemon(QStringList());daemon->moveToThread(this);//daemon->setProPaths(list);//切勿在主线程直接调用依附于子线程的daemon对象的成员函数,可以尝试使用QMetaObject::invoke()函数this->start();
}DaemonThread::~DaemonThread()
{daemon->deleteLater();this->quit();this->wait();
}

这种情况下主要在QThread子类(DaemonThread)的构造函数和析构函数中假如自己的处理,更加方便对依附于子线程的Daemon类对象进行管理。

2.子类化QThread并重写run()函数

这时我们一般不开启子线程的事件循环,我们可以通过其它机制和子线程交互。

这里我们使用《C++ GUI Qt4 Programming》中线程章节给出的框架进行讲解,框架的大致结构是将费时费力的运算打包成一个类,我们称之为算法类,算法类继承于同一个基类,区别在于对apply()虚函数的重写以及类数据成员,子线程中只需调用算法类的apply函数即可实现多态;子线程和主线程交互通过共同维护一个算法类的队列,当队列为空时子线程休眠,当主线程向队列添加新的算法类对象时,子线程被唤醒,处理完毕后接着休眠,当主线程向队列添加的算法类对象包含结束信号时,子线程结束。

transactionthread.h文件:

#ifndef TRANSACTIONTHREAD_H
#define TRANSACTIONTHREAD_H#include <QMutex>
#include <QQueue>
#include <QThread>
#include <QWaitCondition>class Transaction
{
public:virtual ~Transaction() { }virtual void apply() = 0;
private:
};class LocationTvTransaction : public Transaction
{
private:QString openSourceFileName = QString::null;QString tempJpgFileName = QString::null;QString saveResultFileName = QString::null;
public:LocationTvTransaction(QString openSourceFileName, QString tempJgpFileName, QString saveResultFileName);void apply();
};/*class OnlineTvTransaction : public Transaction
{
private:QString saveSourceFileName = QString::null;QString saveResultFileName = QString::null;
public:OnlineTvTransaction(QString saveSourceFileName, QString saveResultFileName);void apply();
};*/class TransactionThread : public QThread
{Q_OBJECTpublic:TransactionThread();~TransactionThread();void addTransaction(Transaction *transact);signals:
//    void transactionStarted(const QString &message);void allTransactionsDone(QString);protected:void run();private:QQueue<Transaction *> transactions;QWaitCondition transactionAdded;QMutex mutex;
};#endif

Transaction类为算法类的基类,同时也是一个抽象类,LocationTvTransaction类为具体的算法类,主要功能是处理本地视频,OnlineTvTransaction类为处理在线视频的算法类,这里还未实现,TransactionThread类为QThread的子类,其中QQueue<Transaction *>transactions成员就是要维护的算法类对象队列;

#include "transactionthread.h"
#include <string>
#include <QFileInfo>
#include <iostream>
#include <exception>
#include <qDebug>
#include <iostream>Transaction * const EndTransaction = 0;void LocationTvTransaction::apply()
{bool ret = true;ret = false;ret = choose_mat(openSourceFileName.toStdString(), tempJpgFileName.toStdString(), 2);if(ret == false){qCritical("choose_mat failed!");return;}ret = choose_with_color(tempJpgFileName.toStdString(), saveResultFileName.toStdString());if(ret == false){qCritical("choose_with_color failed!");}
}LocationTvTransaction::LocationTvTransaction(QString openSourceFileName, QString tempJpgFileName, QString saveResultFileName)
{this->openSourceFileName = openSourceFileName;this->tempJpgFileName = tempJpgFileName;this->saveResultFileName = saveResultFileName;
}TransactionThread::TransactionThread()
{start();
}TransactionThread::~TransactionThread()
{{QMutexLocker locker(&mutex);while (!transactions.isEmpty())delete transactions.dequeue();transactions.enqueue(EndTransaction);transactionAdded.wakeOne();}wait();
}void TransactionThread::addTransaction(Transaction *transact)
{QMutexLocker locker(&mutex);transactions.enqueue(transact);transactionAdded.wakeOne();
}void TransactionThread::run()
{Transaction *transact = 0;forever{{QMutexLocker locker(&mutex);while (transactions.isEmpty())transactionAdded.wait(&mutex);transact = transactions.dequeue();if (transact == EndTransaction)break;}transact->apply();delete transact;{QMutexLocker locker(&mutex);if (transactions.isEmpty()){emit allTransactionsDone("result");}}}
}

LocationTvTransaction类的apply()函数调用了外部库提供的两个处理视频文件的函数,这里主要看Transaction::run()函数和Transaction::addTransaction()函数的实现,主线程和子线程的交互主要在这里实现,run函数中是一个死循环,操作队列之前,先上锁,然后检测队列是否为空,如果为空,调用transactionAdded.wait(&mutex)临时解锁,并休眠线程,等待主线程添加算法类对象到队列,主线程调用addTransaction()函数添加对象到队列后,会调用transaction::wakeOne()函数唤醒子线程,子线程唤醒后先重新获取mutex锁,锁定之后transactionAdded.wait(&mutex)函数才会返回,这样可以保证线程永久安全,返回之后会重新检测是否为空,这里一定不会为空,所以while可以改成if,之后子线程开始算法处理。

QThread Qt相关推荐

  1. 第十四章:Qt网络编程

    回顾: 第一章:Qt的概述 第二章:在Ubuntu编写第一个Qt程序 第三章:Qt的字符串和字符编码 第四章:Qt的信号和槽 第五章:Qt容器窗口(父窗口) 第六章:面向对象的Qt编程 第七章:Qt设 ...

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

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

  3. pyqt5能否用于鸿蒙系统,PyQt显示来自opencv的视频流

    感谢Taimur Islam的提问.感谢eyllanesc的精彩回答,我对你的代码进行了一些修改.我使用PtQt = 4 Python = 2.7并且我没有使用opencvimport sys imp ...

  4. PyQt5学习例子整理

    基本框架 # PyQt5引入的组件其实只需要QtWidgets.QApplication就可以实现最基本的窗体显示 # 还需要引入sys作为窗体应用进程的控制 from PyQt5.QtWidgets ...

  5. Python Qt GUI设计:QTimer计时器类、QThread多线程类和事件处理类(基础篇—8)

    目录 1.QTimer计时器类 2.QThread多线程类 3.事件处理类 一般情况下,应用程序都是单线程运行的,但是对于GUI程序来说,单线程有时候满足不了需求.例如,如果需要执行一个特别耗时的操作 ...

  6. Qt中使用多线程的一些心得(一)——继承QThread的多线程使用方法

    一 前言 二Qt多线程方法一 继承QThread 2.1使用多线程的原因分析 2.2 写一个继承于QThread的线程 三 总结 一 前言   本篇文章部分内容参考了该博文:传送门.   Qt中有两种 ...

  7. 在Qt(C++)中使用QThread实现多线程

    1. 引言 多线程对于需要处理耗时任务的应用很有用,一方面响应用户操作.更新界面显示,另一方面在"后台"进行耗时操作,比如大量运算.复制大文件.网络传输等. 使用Qt框架开发应用程 ...

  8. Qt修炼手册11_多线程编程和QThread类

    1.事件循环 学习QT多线程编程之前,有必要先熟悉事件循环的概念. 先看一个单线程界面程序的主函数代码: int main(int argc, char* argv[]) {QApplication ...

  9. Qt: QTimer和QThread

    让QTimer 跑在其他线程. 一般写法如下. 1. 在main thread中为worker thread指定定时器. QThread* thread = new QThread(this);thr ...

最新文章

  1. 使用windows.name解决js跨域数据通信
  2. 好程序员Web前端分享程序的三大结构(二)while循环
  3. Java引用类型变量如何分配内存空间?
  4. python创建tcp socket_Python Socket如何建立TCP连接
  5. 高德地图定位精度多少米_中美俄卫星定位精度分别是多少?美0.1米,俄10米,中国呢?...
  6. MapReduce算法设计(三)----相对频率计算
  7. BIO ,NIO,AIO的区别
  8. set和map去重调用什么方法_Es6中Map对象和Set对象的介绍及应用
  9. 搭建深度学习推荐系统实战
  10. greenshot滚屏截图_Greenshot是一款免费的轻量级屏幕截图实用程序,具有许多有用的功能...
  11. 使用pdfbox实现PDF转JPG
  12. 基于YOLO的新型RGB-D融合方法对行人进行检测和3D定位
  13. python用input输入整数列表_python中,用input()输入一个整数
  14. IDOC的处理函数IDOC_INPUT_ORDERS的增强点的分析
  15. seo技巧,seo技巧搜行者SEO
  16. 27.FastAPI应用生产环境部署
  17. 微信内测推出新功能,朋友圈“修改可见范围”
  18. react中ref使用方法解析
  19. 流程图分级、分类、分层
  20. 我的世界java版mac切视角_我的世界怎么视角切换攻略 第三人称视角

热门文章

  1. 【渝粤题库】陕西师范大学201771 中国古代文学(一) 作业
  2. ubuntu18纯净系统个人配置
  3. 基于SpringBoot的智能物流监控系统数据系统
  4. python关于类的通俗描述?
  5. 手机智能控制汽车系统作用详解
  6. 蓝桥杯 基础练习(三)字母图形 C语言
  7. cad2006安装未找到html文件,我的CAD已安装在D驱动器上,但是在打开dwg文件时,它提示找不到C...
  8. 2022年危险化学品经营单位安全管理人员考题及答案
  9. html提示用Safari浏览器打开,Safari浏览器无法打开网页怎么办 Safari打不开网站原因及解决方法...
  10. 洛谷P1498 南蛮图腾(递归,找规律)