源码见 testQThread_QTimer...
Qt 版本5.6.3
视频讲解:https://www.bilibili.com/video/BV15P411C79i/
链接: 视频讲解
简介
想法很单纯,就是主线程启动一个子线程,子线程里启动一个定时器,定时执行一些任务,然鹅实际开发中警告不断。😒Qt
警告内容有:
1.QObject: Cannot create children for a parent that is in a different thread.
2.QObject::killTimer: Timers cannot be stopped from another thread
3.QObject::moveToThread: Cannot move objects with a parent
对线程和线程对象的理解
线程对象是一个内核对象,就和常用的打开文件会获得一个文件句柄的句柄都属于稀缺的资源。线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程可以包含多个线程,每个线程可以并发执行不同的任务。
一个进程一定有一个主线程 ,子线程是从主线程启动的。线程的关键特性除了并发性其实还有一个比较隐晦,就是线程的模块性。这是因为线程执行内容总应该是负责某块功能,具有一定独立性。举例来说,main.exe
启动一个独立的线程来定时检查更新,显然这个线程负责的功能就很明确,如果更新检查除了问题,就检查这个线程涉及的所有代码。
线程的退出:一般情况,线程干完活就自动释放了。还是拿上面的检查线程来说,如果他的使命是检查5次是否有更新,则检查完5次,没干的了,这个线程就会退了。除非是一直检查,这个线程才会一直运行下去。
关于QThread
QThread
这个类封装了对线程对象的管理,相当于我们雇佣了一个工人,需要发个工牌来知道都有哪些工人,通过工牌的工号发布指令。显然,如果只有一个主线程就不需要了,因为系统已经干了。
具体到QThread
的用法,主流的就2种。后续有代码解析。
方法一:继承QThread,并重写run方法、
方法二:将代码逻辑封装为一个类,
关于QTimer
QTimer
用于执行一些定时任务,往往采用信号槽的方式,连接timeout()函数至槽函数。
QTimer
的启动、停止、创建、销毁都需在同一个线程中。主线程中用的很easy,一旦涉及到子线程使用难度就上来了,需要保证QTimer
的创建、启动、停止、销毁都是在同一线程。而往往我们都是在各种槽函数中对定时器停止。
Qt 槽函数到底执行在哪个线程?
解决开篇警告的核心是搞清楚槽函数执行在哪个线程,以及为啥执行在那个线程。
这个答案来于互联网,然而我实测的结果不是这样的。
在多线程的Qt程序中,一个槽函数的执行线程是由发出信号的线程决定的,而不是连接该信号的线程。
具体来说:
- 如果信号是在主线程发出的,则槽函数会在主线程执行,即使该槽函数是在其他线程中连接的。
- 如果信号是在非GUI线程发出的,则槽函数会在该非GUI线程执行,即使是在主线程中连接的。
- 槽函数总是会在信号发出的线程中执行。
** 构造函数的执行顺序
看似简单,我只要保证emit是在哪个线程即可。但实际开发中涉及类的组合关系,A类的成员类B类,在调用A类的构造之前会先调用B类的构造,B类的构造,而B类的构造还是在上一级的线程中。这也就导致了类A被moveToThread
之后成了线程m_thred
的资源,而A的B成员则还是主线程的资源。
c++
class CB{
public:
CB(){}
private:
}
class CA{
public:
CA(){ moveToThread(&m_thread); }
private:
CB m_b;
QThread m_thread;
}
总结
槽函数执行在哪个线程取决于发出信号的对象和槽函数的对象是在哪个线程构造的。要想保证成员类的槽函数都执行在子线程,成员类的构造就需在子线程,也就是成员类要以指针的方式组合,在合适的时机new对象出来。