这两天在弄Qt的多线程,简要记录一些心得。
ref
简述
Qt有两种多线程方法,一种是继承QThread的run函数,另一种是把继承于QObject的类转移到新的Thread里。Qt4.8一般采用继承QThread的方法,但在4.8之后官方建议采用第二种方法,使用会更加灵活一些。
网络上较多的方法是采用第一种,包括很多书籍教程也是,这里把两种方法都进行下记录。
通常,应用程序都是在一个线程中执行的,但当调用一个好事操作,例如大批量Io或大量矩阵变换等CPU密集操作时,用于界面常常会冻结,可以使用多线程来解决这些问题。
优势
- 提高应用程序的响应速度,尤其对于GUI来说,如果某个操作耗时很长,整个系统都在等待,将不能响应鼠标、键盘、菜单等操作。利用多线程可以将耗时操作放到一个新的线程中,避免该问题。
- 使多CPU更加有效
- 改善程序结构,将复杂的进行拆成多个线程,成为独立或半独立的运行部分,有利于代码的理解和维护。
不足
- 行为无法预期,多次执行结果可能不尽相同
- 多线程的执行顺序无法保证,与操作系统地调度策略和线程优先级有关
- 多线程的切换可能发生在任何时间和地点
- 对代码的敏感度较高,细微修改都可能造成意向不到的结果
继承QThread
使用Qthread时有一个重要的规则,QThread只有run函数是在新线程里的,其他所有函数都在QThread生成的线程里。之前某个写法一直阻塞UI就是因为我在前天函数进行了操作,但其他函数并不是在子线程中的。所以要注意以下两点:
- 如果QThread是在ui所在线程里成成,那么QThread的其他非run函数都是和ui一样的,要确保耗时的操作都在run函数中完成。
在UI线程下调用QThread的非run函数和执行普通函数没有区别。但要注意,如果这2. 个函数对线程的某个变量进行变更,并且该变量在run函数中也会使用,就要注意同步的问题,因为虽然都是QThread的类里,但run和非run函数实际上两个不同线程进行的,之间可能有竞争。
任何继承于QThread的线程都是通过继承QThread的run函数来实现多线程的,因此,必须重写QThread的run函数,把复杂逻辑写在QThread的run函数中。
参考博客提供的代码,如下,稍微替换了下命名风格。该类继承自QThread,包含了Qt类的常见内容,包括普通方法,信号和槽,还有run函数。其中SetSth, GetSth, DoSth
函数都进行了500ms的延迟,用于验证在QThread::run()
之外调用QThread的成员函数并会在新的线程中运行。
ThreadFromQThread.h
|
ThreadFromQThread.cpp
|
博主提供了如下的UI界面,用于验证:
- UI线程中调用SetSth和GetSth,是否会阻塞UI线程
- 调用quit函数和exit是否会停止子线程
- 调用terminte是否会停止子线程
基本效果如下
UI界面上提供一个进度条,由主程序的定时器控制,每100ms触发一次,用于证明UI线程是否阻塞。
第二个进度条由子线程控制。
run按钮会运行子线程。
点击getSth, setSth, doSth等按钮时候,都是调用子线程类中的非run函数,可以看到对应的thread ID其实并不是子线程的ID。
并且点击quit,thread没有任何响应,QThread在不调用exec()的情况下的exit和quit函数不会起作用。
点击terminate按钮,线程立即终止。
总结一下:
- 正确终止子线程
在子线程类中添加bool变量,主线程修改该变量然后在线程的run函数中判断是否退出。注意要用QMutexLocker枷锁判断,避免线程冲突。 - 正确启动线程
- 全局线程
方法一:在窗口中创建,在窗口析构时要先调用线程的wait函数,等待子线程完全结束
方法二:创建时连接connect(thread, &QThread::finished, &QObject::deleteLater;
,父对象为NULL
,在线程结束时自行销毁(消息循环中确认没有该对象后析构) - 局部线程
采用以上方法二即可
- 全局线程
- 重复创建子线程时,可能要注意重复生成还有析构,以及时切换到新线程中完成任务,参考代码。
继承QObject
Qt官方比较推荐继承QObject的方法来实现,更加灵活。
QObject是Qt框架的基本类,但凡涉及到信号槽有关的类都是继承于QObject。QObject是一个功能异常强大的类,它提供了Qt关键技术信号和槽的支持以及事件系统的支持,同时它提供了线程操作的接口,也就是QObject是可以选择不同的线程里执行的。
QObject的线程转移函数是:void moveToThread(QThread * targetThread) ,通过此函数可以把一个顶层Object(就是没有父级)转移到一个新的线程里。
QThread非常容易被新手误用,主要是QThread自身并不生存在它run函数所在的线程,而是生存在旧的线程中,此问题在上一篇重点描述了。由于QThread的这个特性,导致在调用QThread的非run函数容易在旧线程中执行。