搜索
bottom↓
回复: 4

嵌入式Linux应用程序开发-(6)嵌入式QT多线程的简单实现...

[复制链接]

出0入0汤圆

发表于 2019-4-22 15:06:48 | 显示全部楼层 |阅读模式
本帖最后由 广轻电气091 于 2019-4-22 15:14 编辑

嵌入式QT多线程的简单实现(方法二)
本文的内容是拜读完以下文章后的总结,喝水不忘挖井人,感谢前辈的肩膀,让我们这些晚辈少走弯路,走得更远。如果已经理解了原作者的文章,则可完全忽略本文,感谢支持和关注。
https://blog.csdn.net/czyt1988/article/details/71194457

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

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,类的具体成员变量和成员函数,如下所示:
  1. class ThreadObject : public QObject
  2. {
  3.     Q_OBJECT

  4. signals:
  5.     void message(const QString& info);  //通过此信号,发送需要打印的message消息
  6.     void progress(int present);  //通过此信号,发送ProgressBar的进度百分比

  7. public:
  8.     ThreadObject(QObject* parent = NULL);
  9.     ~ThreadObject();
  10.     void setRunCount(int count);  //设置运行次数
  11.     void stop();


  12. public slots:
  13.     void runSomeBigWork1();     //线程的while循环,在里面执行耗时任务
  14.     void runSomeBigWork2();      //线程的while循环,在里面执行耗时任务

  15. private:
  16.     int m_runCount;       //while循环的运行次数
  17.     int m_runCount2;      //while循环的运行次数
  18.     bool m_isStop;        //线程停止标志位

  19.     QMutex m_stopMutex;

  20.     void msleep(unsigned int msec);  //线程休眠函数
  21. };
复制代码


4、创建一个ThreadObject.cpp文件,ThreadObject类里面的成员函数,均在这个文件里面实现。代码如下所示:
  1. //线程的耗时操作都在这里进行
  2. void ThreadObject::runSomeBigWork1()
  3. {
  4.     {
  5.         QMutexLocker locker(&m_stopMutex);
  6.         m_isStop = false;
  7.     }

  8.     int count = 0;
  9.     QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());
  10.     emit message(str);
  11.     int process = 0;
  12.     emit progress(process);
  13.     while(1)
  14.     {
  15.         {
  16.             QMutexLocker locker(&m_stopMutex);
  17.             if(m_isStop)    //通过m_isStop变量来退出线程
  18.                 return;
  19.         }

  20.         int pro = ((float)count / m_runCount) * 100;
  21.         if(pro != process)
  22.         {
  23.             process = pro;
  24.             emit progress(((float)count / m_runCount) * 100);   //线程运行的百分比
  25.             emit message(QString("Object::run times:%1,m_runCount:%2").arg(count).arg(m_runCount));
  26.         }
  27.         msleep(1000);
  28.         count++;

  29.         if(m_runCount < count)   //线程达到最大的运行次数,则退出
  30.         {
  31.             break;
  32.         }
  33.     }
  34. }

  35. //线程的耗时操作都在这里进行
  36. void ThreadObject::runSomeBigWork2()
  37. {
  38.     {
  39.         QMutexLocker locker(&m_stopMutex);
  40.         m_isStop = false;
  41.     }

  42.     int count = 0;
  43.     QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());
  44.     emit message(str);
  45.     int process = 0;
  46.     QElapsedTimer timer;
  47.     timer.start();
  48.     while(1)
  49.     {
  50.         {
  51.             QMutexLocker locker(&m_stopMutex);
  52.             if(m_isStop)
  53.                 return;
  54.         }

  55.         int pro = ((float)count / m_runCount2) * 100;
  56.         if(pro != process)
  57.         {
  58.             process = pro;
  59.             emit progress(pro);
  60.             emit message(QString("%1,%2,%3,%4").arg(count).arg(m_runCount2).arg(pro).arg(timer.elapsed()));
  61.             timer.restart();
  62.         }

  63.         msleep(1000);
  64.         count++;

  65.         if(m_runCount2 < count)   //线程达到最大的运行次数,则退出
  66.         {
  67.             break;
  68.         }
  69.     }
  70. }
复制代码

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

5、在widget.cpp文件中,ui窗口进行构造时,先设置进度条的初始状态,并关联定时器的超时槽函数,这个定时器用来监控ui线程是否卡死。代码如下所示:
  1. Widget::Widget(QWidget *parent) :
  2.     QWidget(parent),
  3.     ui(new Ui::Widget)
  4. {
  5.     ui->setupUi(this);

  6.     m_obj = NULL;
  7.     m_objThread = NULL;

  8.     ui->progressBar->setRange(0,100);
  9.     ui->progressBar->setValue(0);
  10.     ui->progressBar_heart->setRange(0,100);
  11.     ui->progressBar_heart->setValue(0);

  12.     //启动一个定时器,用来监视ui线程是否有卡死
  13.     connect(&m_heart,SIGNAL(timeout()),this,SLOT(heartTimeOut()));
  14.     m_heart.setInterval(100);

  15.     m_heart.start();   //启动定时器,不断更新heartbeat进度条

  16.     //打印出 ui 的线程id
  17.     receiveMessage(QString("%1->%2,current ui id is:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));
  18. }
复制代码


6、点击 [moveToThread run 1] 或 [moveToThread run 2] 按钮,即可调用startObjThread()启动线程,然后通过发送信号,唤起线程耗时操作的槽函数。startObjThread()函数的具体实现如下所示:
  1. //使用moveToThread的方式启动一个线程
  2. void Widget::startObjThread()
  3. {
  4.     if(m_objThread)   //如果这个线程已经存在,则不再启动
  5.     {
  6.         return;
  7.     }
  8.     m_objThread= new QThread();    //创建一个QThread对象
  9.     m_obj = new ThreadObject();    //使用继承于QObject的类创建一个对象
  10.     m_obj->moveToThread(m_objThread);    //把继承于QObject的类对象转移到新的线程运行

  11.     //关联deleteLater槽函数,这是线程安全退出的关键
  12.     connect(m_objThread,SIGNAL(finished()),m_objThread,SLOT(deleteLater()));
  13.     connect(m_objThread,SIGNAL(finished()),m_obj,SLOT(deleteLater()));

  14.     //关联两个信号,用于启动线程里面的耗时操作
  15.     connect(this,SIGNAL(startObjThreadWork1()),m_obj,SLOT(runSomeBigWork1()));
  16.     connect(this,SIGNAL(startObjThreadWork2()),m_obj,SLOT(runSomeBigWork2()));

  17.     //关联两个信号,用于更新进度条和打印信息
  18.     connect(m_obj,SIGNAL(progress(int)),this,SLOT(progress(int)));
  19.     connect(m_obj,SIGNAL(message(QString)),this,SLOT(receiveMessage(QString)));

  20.     m_objThread->start();
  21. }
复制代码

这里需要注意,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均由同一个线程管理并运行。
(7)stop()函数与新建线程不在同一个线程内,stop()函数是归属于ui线程的。

点击这里,下载工程源码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

 楼主| 发表于 2019-4-23 08:13:39 | 显示全部楼层
本章节pdf下载:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入170汤圆

发表于 2019-4-23 09:04:05 | 显示全部楼层

楼主,我看着你的教程,卡在了qmake,新手

出0入0汤圆

 楼主| 发表于 2019-4-23 09:17:39 | 显示全部楼层
穿越时空 发表于 2019-4-23 09:04
楼主,我看着你的教程,卡在了qmake,新手

你使用的是哪个平台?遇到什么问题了?

出0入0汤圆

 楼主| 发表于 2019-4-29 13:57:36 | 显示全部楼层
源码下载路径已更改:点击这里
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-25 16:06

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表