找回密码
 立即注册
  • QQ空间
  • 回复
  • 收藏

(6)嵌入式QT多线程的简单实现(方法二)

admin 2019-8-12 20:09 130人围观 Qt相关

点击上方蓝字,关注微联智控

可点击右上角的 …,分享这篇文章

        本文的内容是拜读完以下文章后的总结,喝水不忘挖井人,感谢前辈的肩膀,让我们这些晚辈少走弯路,走得更远。如果已经理解了原作者的文章,则可完全忽略本文,感谢阅读。

https://blog.csdn.net/czyt1988/article/details/71194457

        上一篇文章介绍了使用继承QThread类,重载run()函数的方法来实现多线程,这种方法是QT实现多线程编程的传统方法,但从QT4.8以后的版本开始,QT官方并不是很推荐使用这种方法,而是极力推荐使用继承QObject类的方法来实现多线程,因为QObject类比QThread类更加灵活。当然,使用哪种方法实现多线程,需要根据具体的实际情况而定。

        QObject类是QT框架里面一个很重要的基本类,除了QT的关键技术信号与槽,QObject还提供了事件系统和线程操作接口的支持。对QObject类,可以使用QObject类里面的方法moveToThread(QThread *targetThread),把一个没有父级的继承于QObject的类转移到指定的线程中运行。

        使用继承QObject来实现多线程,默认支持事件循环(QT里面的QTimer、QTcpSocket等等,均支持事件循环)。而如果使用QThread类来实现多线程,则需要调用QThread::exec()来支持事件循环,否则,那些需要事件循环支持的类都无法实现信号的发送。因此,如果要在多线程中使用信号和槽,那就直接使用QObject来实现多线程。

        不管使用方法一还是方法二,在QT中创建多线程是比较简单的,难点在于如何安全地退出线程和释放线程资源。在创建完线程之后,使用方法二(继承QObject)来创建的线程,不能在ui线程(主线程)中直接销毁,而是需要通过deleteLater() 进行安全销毁。

        先来总结一下,如何使用继承QObject的方法来创建多线程:

(1)写一个继承QObject的类,并把复杂耗时的操作声明为槽函数。

(2)在主线程(ui线程)中new一个继承Object类的对象,不设置父类。

(3)声明并new一个QThread对象。(如果QThread对象没有new出来,则需要在QObject类析构的时候使用QThread::wait()等待线程完成。如果是通过堆分配(new方式),则可以通过deleteLater来进行释放)

(4)使用QObject::moveToThread(QThread*)方法,把QObject对象转移到新的线程中。

(5)把线程的finished()信号与QObject的deleteLater()类连接,这个是安全释放线程的关键,不连接的话,会导致内存泄漏。

(6)连接其他信号槽,如果是在连接信号槽之前调用moveToThread,不需要处理connect函数的第五个参数,这个参数是表示信号槽的连接方式;否则,如果在连接所有信号槽之后再调用moveToThread,则需要显式声明Qt::QueuedConnection来进行信号槽连接。

(7)完成一系列初始化后,调用QThread::start()来启动线程。

(8)在完成一系列业务逻辑后,调用QThread::quit()来退出线程的事件循环。

使用继承QObject的方法来创建和启动线程,这种方法比使用QThread来创建线程要灵活得多。使用这种方法创建线程,整个线程对象都在新的线程中运行,而不像QThread那样,只有run()函数在新的线程中运行。QObject自身的信号槽和事件处理机制,可以灵活地应用到业务逻辑中。下面,我们基于方法一(继承QThread)的例程,使用继承QObject的方法来创建线程。

以下是应用程序的开发过程

1、先用Qt Creator构建一个工程,命名为:006_qthread_object,关于如何构建工程,请参考“第一个嵌入式QT应用程序”的具体内容。

2、双击打开“widget.ui”文件,构建界面,构建后的界面如下图所示:



界面描述:

[moveToThread run 1]:发送信号启动耗时任务1

[moveToThread run 2]:发送信号启动耗时任务2

[stop thread run]:修改变量,退出耗时任务的运行

[Clear Browser]:清空信息显示窗口

3、创建一个ThreadObject.h头文件,在头文件定义一个继承于QOBject的类ThreadObject,类的具体成员变量和成员函数,如下所示:



4、创建一个ThreadObject.cpp文件,ThreadObject类里面的成员函数,均在这个文件里面实现。代码如下所示:





线程的耗时操作都在这两个函数中进行,这两个函数都是通过ui线程中的信号进行触发的。同时,为了对线程进行停止,这里使用了一个m_isStop的成员变量。通过在ui线程中,调用stop()函数修改m_isStop变量,达到退出线程的目的。注意,修改m_isStop变量时,要进行互斥锁操作。

5、在widget.cpp文件中,ui窗口进行构造时,先设置进度条的初始状态,并关联定时器的超时槽函数,这个定时器用来监控ui线程是否卡死。代码如下所示:



6、点击 [moveToThread run 1] 或 [moveToThread run 2] 按钮,即可调用startObjThread()启动线程,然后通过发送信号,唤起线程耗时操作的槽函数。startObjThread()函数的具体实现如下所示:



这里需要注意,QThread对象是通过new方法在堆内创建的,线程finished()退出的时候,需要关联deleteLater槽函数进行线程的安全退出和线程资源回收。否则,线程所占用的内存资源就不会进行释放,长时间下去,会造成内存资源泄漏。

7、至此,使用继承QObject这种方法来实现多线程已经介绍完毕,这种方法比使用继承QThread更加方便灵活,使用这种方法的总结如下:

(1)如果线程里面使用到消息循环,信号槽,事件队列等等操作,建议使用QObject来创建线程。

(2)继承QObject的类对象,都不能指定其父对象。

(3)新线程里面耗时操作,都定义为槽函数,方便通过信号启动线程。

(4)操作线程里面的成员变量时,为了安全,需要加锁后再进行操作。

(5)互斥锁QMutex会带来一点性能损耗。

8、把程序编译完后,下载到开发板运行,运行现象如下图所示:



实验现象说明:

(1)程序开始运行时,先打印出ui的线程ID。

(2)点击[moveToThread run 1]按钮,耗时任务work1开始运行,并打印出运行的线程ID。

(3)点击[moveToThread run 2]按钮,耗时任务work2开始运行,并打印出运行的线程ID。

(4)点击[stop thread run]按钮,耗时任务停止运行,并打印出stop()函数所在的线程ID。

(5)在work1运行期间,如果开启work2任务,则work1任务会中断,反之亦然。

(6)任务work1和任务work2均由同一个线程管理并运行。stop()函数与新建线程不在同一个线程内,stop()函数是归属于ui线程的。

-- END --




长按识别二维码,关注微联智控




----------------------------------------------------------------------------------------------------------------------
我们尊重原创,也注重分享,文章来源于微信公众号:微联智控,建议关注公众号查看原文。如若侵权请联系qter@qter.org。
----------------------------------------------------------------------------------------------------------------------

鲜花

握手

雷人

路过

鸡蛋

yafeilinux和他的朋友们微信公众号二维码

微信公众号

专注于Qt嵌入式Linux开发等。扫一扫立即关注。

Qt开源社区官方QQ群二维码

QQ交流群

欢迎加入QQ群大家庭,一起讨论学习!

我有话说......