1. 引言

多线程对于需要处理耗时任务的应用很有用,一方面响应用户操作、更新界面显示,另一方面在“后台”进行耗时操作,比如大量运算、复制大文件、网络传输等。
使用Qt框架开发应用程序时,使用QThread类可以方便快捷地创建管理多线程。而多线程之间的通信也可使用Qt特有的“信号-槽”机制实现。
下面的说明以文件复制为例。主线程负责提供交互界面,显示复制进度等;子线程负责复制文件。最后附有可以执行的代码。

2. QThread使用方法1——重写run()函数

第一种使用方法是自己写一个类继承QThread,并重写其run()函数。
大家知道,C/C++程序都是从main()函数开始执行的。main()函数其实就是主进程的入口,main()函数退出了,则主进程退出,整个进程也就结束了。
而对于使用Qthread创建的进程而言,run()函数则是新线程的入口,run()函数退出,意味着线程的终止。复制文件的功能,就是在run()函数中执行的。
下面举个文件复制的例子。自定义一个类,继承自Qthread

CopyFileThread: public QThread
{Q_OBJECT
public:CopyFileThread(QObject * parent = 0);protected:void run(); // 新线程入口
// 省略掉一些内容
}

在对应的cpp文件中,定义run()

void CopyFileThread::run()
{// 新线程入口// 初始化和操作放在这里
}

将这个类写好之后,在主线程的代码中生成一个CopyFileThread的实例,例如在mainwindow.cpp中写:

// mainwindow.h中
CopyFileThread * m_cpyThread;// mainwindow.cpp中
m_cpyThread = new CopyFileThread;

在要开始复制的时候,比如按下“复制”按钮后,让这个线程开始执行:

m_cpyThread->start();

注意,使用start()函数来启动子线程,而不是run()。start()会自动调用run()。
线程开始执行后,就进入run()函数,执行复制文件的操作。而此时,主线程的显示和操作都不受影响。
如果需要进行对复制过程中可能发生的事件进行处理,例如界面显示复制进度、出错返回等等,应该从CopyFileThread中发出信号(signal),并事先连接到mainwindow的槽,由这些槽函数来处理事件。

3. QThread使用方法2——moveToThread()

如果不想每执行一种任务就自定义一个新线程,那么可以自定义用于完成任务的类,并让它们继承自QObject。例如,自定义一个FileCopier类,用于复制文件。

class FileCopier : public QObject
{Q_OBJECT
public:explicit FileCopier(QObject *parent = 0);public slots:void startCopying();void cancelCopying();
}

注意这里我们定义了两个槽函数,分别用于复制的开始和取消。
这个类本身的实例化是在主线程中进行的,例如:

// mainwindow.h中
private:FileCopier* m_copier;// mainwindow.cpp中,初始化时m_copier = new FileCopier;

此时m_copier还是属于主线程的。要将其移动到子线程处理,需要首先声明并实例化一个QThread:

// mainwindow.h中
signals:void startCopyRsquested();
private:QThread * m_childThread; // m_copier将被移动到此线程执行// mainwindow.cpp中,初始化时m_childThread = new QThread; // 子线程,本身不负责复制

然后使用moveToThread()将m_copier移动到新线程。注意moveToThread()是QObject的公有函数,因此用于复制文件的类FileCopier必须继承自QObject。移动之后启动子线程。此时复制还没有开始。

    m_copier->moveToThread(m_childThread); // 将实例移动到新的线程,实现多线程运行m_childThread->start(); // 启动子线程

注意一定要记得启动子线程,否则线程没有运行,m_copier的功能也无法执行。
要开始复制,需要使用信号-槽机制,触发FileCopier的槽函数实现。因此要事先定义信号并连接:

// mainwindow.h中
signals:void startCopyRsquested();
// mainwindow.cpp中,初始化时
// 使用信号-槽机制,发出开始指令connect(this, SIGNAL(startCopyRsquested()), m_copier, SLOT(startCopying()));

当按下“复制”按钮后,发出信号。

    emit startCopyRsquested(); // 发送信号

m_copier在另一个线程接收到信号后,触发槽函数,开始复制文件。

4.常见问题

4.1. 子线程中能不能进行UI操作?

Qt中的UI操作,比如QMainWindow、QWidget之类的创建、操作,只能位于主线程!
这个限制意味着你不能在新的线程中使用QDialog、QMessageBox等。比如在新线程中复制文件出错,想弹出对话框警告?可以,但是必须将错误信息传到主线程,由主线程实现对话框警告。
因此一般思路是,主线程负责提供界面,子线程负责无UI的单一任务,通过“信号-槽”与主线程交互。

4.2. QThread中的哪些代码属于子线程?

QThread,以及继承QThread的类(以下统称QThread),他们的实例都属于新线程吗?答案是:不。
需要注意的是,QThread本身的实例是属于创建该实例的线程的。比如在主线程中创建一个QThread,那么这个QThread实例本身属于主线程。当然,QThread会开辟一个新线程(入口是run()),但是QThread本身并不属于这个新线程。也就是说,QThread本身的成员都不属于新线程,而且在QThread构造函数里通过new得到的实例,也不属于新线程。这一特性意味着,如果要实现多线程操作,那么你希望属于新线程的实例、变量等,应该在run()中进行初始化、实例化等操作。本文给出的例子就是这样操作的。
如果你的多线程程序运行起来,会出现关于thread的报警,思考一下,各种变量、实例是不是放对了位置,是不是真的位于新的线程里。

4.3. 怎么查看是不是真的实现了多线程?

可以打印出当前线程。对于所有继承自QObject的类,例如QMainwindow、QThread,以及自定义的各种类,可以调用QObject::thread()查看当前线程,这个函数返回的是一个QThread的指针。例如用qDebug()打印:
在mainwindow.cpp的某个函数里、QThread的run()函数里、自定义类的某个函数里,写上:

qDebug() << "Current thread:" << thread();

对比不同位置打印的指针,就可以知道它们是不是位于同一个线程了。

5.范例

范例实现了多线程复制文本文件。
提供的范例文件可用QtCreator编译运行。界面如下(不同的操作系统略有不同):

范例中实现了本文介绍的两种方法,同时也给出了单线程复制对比。打钩选择不同的复制方法。可以发现,在使用多线程的时候,界面不会假死,第二根进度条的动画是持续的;而使用单线程复制的时候,“取消”按钮按不动,界面假死,而且第二根进度条的动画也停止了。
由于范例处理的文件很小,为了让复制过程持续较长时间以便使得现象明显,复制文件的时候,每复制一行加入了等待。

范例代码:
https://github.com/Xia-Weiwen/CopyFile

转载于:https://www.cnblogs.com/xia-weiwen/p/10306089.html

在Qt(C++)中使用QThread实现多线程相关推荐

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

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

  2. QT界面中实现视频帧显示的多种方法及应用

    QT界面中实现视频帧显示的多种方法及应用 (一) 引言 1.1 视频帧在QT界面中的应用场景 1.2 不同方法的性能和适用性分析 1.2.1 使用QLabel和QPixmap 1.2.2 使用QPai ...

  3. Qt Quick 中 QML 与 C++ 混合编程详解

    Qt Quick 技术的引入,使得你能够快速构建 UI ,具有动画.各种绚丽效果的 UI 都不在话下.但它不是万能的,也有很多局限性,原来 Qt 的一些技术,比如低阶的网络编程如 QTcpSocket ...

  4. QT之深入理解QThread

    QT之深入理解QThread 理解QThread之前需要了解下QThread类,QThread拥有的资源如下(摘录于QT 5.1 帮助文档): 在以上资源中,本文重点关注槽:start():信号:st ...

  5. 应用笔记-Qt 程序中使用 Live 20R 指纹采集器

    应用笔记-Qt 程序中使用 Live 20R 指纹采集器 最近一个项目需要用到指纹认证.就在某东上随便搜了个销量比较大的指纹采集器,就是今天这个笔记的主角(Live 20R 指纹采集器).花了几天时间 ...

  6. QT解决方案中新建动态链接库工程,且继承于QObject,解决无法生成moc_XXX.cpp文件的问题,解决工程之间的引用问题

    QT解决方案中新建动态链接库工程,且继承于QObject,解决无法生成moc_XXX.cpp文件的问题,解决工程之间的引用问题 参考文章: (1)QT解决方案中新建动态链接库工程,且继承于QObjec ...

  7. Qt/PyQt中使用系统全局的快捷键

    Qt/PyQt中使用系统全局的快捷键 除了全局快捷键部分外,其他的都比较简单,都是我实现"onekeycodehighlighter"中碰到的一些小问题,这里顺面整理一下.事实上, ...

  8. python多线程加速for循环_多线程-如何在Python的循环中对操作进行多线程

    首先,在Python中,如果您的代码受CPU约束,那么多线程将无济于事,因为只有一个线程可以持有全局解释器锁,因此一次只能运行Python代码. 因此,您需要使用进程,而不是线程. 如果您的操作&qu ...

  9. QT发布中遇到的问题 - wufan的专栏 - 博客频道 - CSDN.NET

    QT发布中遇到的问题 - wufan的专栏 - 博客频道 - CSDN.NET QT发布中遇到的问题 分类: qt 2012-04-05 11:15 8人阅读 评论(0) 收藏 举报 最近开始研究qt ...

最新文章

  1. memcache的分布式缓存问题
  2. gpasswd 命令详解
  3. crtlc不能复制文件_ctrl+c不能复制怎么办
  4. JSP自定义标签 函数,实现生日计算年龄
  5. 【C++】 C++标准模板库(十) 双向队列
  6. 按钮 每一行_word跨页表格如何重复设置表头?单击“重复标题行”按钮来设置多页表格重复标题行显示。...
  7. openssh登陆时提示服务器拒绝了密码
  8. 如何在32位程序中突破地址空间限制使用超过4G的内存
  9. easyswoole事务mysql_easyswoole ORM 事务操作管理
  10. JDBC ResultSet 可更新的结果集
  11. 在64位操作系统上使用FlashDevelop的Debug功能
  12. cocos2dx资料汇总 - 持续更新
  13. 安全测试之sql注入
  14. 程序员打完篮球腰疼是怎么回事,打篮球腰疼怎么办,怎么按摩缓解疼痛!
  15. 今天过节,摔杯,逼宫,吃瓜吧?
  16. vue不同页面切换,背景音乐连续播放不间断
  17. 阿里云服务器,修改Apache2默认端口80
  18. Linux下ps命令
  19. Win7屏幕显示方向怎么调整为竖屏显示操作教学分享
  20. 将其他图片转换为ico图片

热门文章

  1. 微信语音怎么转发给别人听_微信语音怎么转发出去?这个小技巧超级好用
  2. android动态设置冷启动图片拉伸变形,Android冷启动时间优化
  3. yii框架的下拉框多选,设置默认值等(dropDownList)
  4. java数组赋值语句,稳进大厂
  5. Android性能优化之启动优化实战篇,最终入职阿里
  6. 【响应式Web前端设计】!important的用法及作用
  7. Python程序设计题解【蓝桥杯官网题库】 DAY8-基础练习
  8. react不同环境不同配置angular_DevOps 前端项目(angular、vue、react)打包静态资源生成一份Docker镜像支持部署不同环境...
  9. mysql怎么查看是否存在死锁_mysql怎么查看有没有死锁
  10. java lists 引用包,在线等java【不在同一类,同一个包的两个LIst可以实现复制么?怎...