|
本帖最后由 广轻电气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,类的具体成员变量和成员函数,如下所示:
- class ThreadObject : public QObject
- {
- Q_OBJECT
- signals:
- void message(const QString& info); //通过此信号,发送需要打印的message消息
- void progress(int present); //通过此信号,发送ProgressBar的进度百分比
- public:
- ThreadObject(QObject* parent = NULL);
- ~ThreadObject();
- void setRunCount(int count); //设置运行次数
- void stop();
- public slots:
- void runSomeBigWork1(); //线程的while循环,在里面执行耗时任务
- void runSomeBigWork2(); //线程的while循环,在里面执行耗时任务
- private:
- int m_runCount; //while循环的运行次数
- int m_runCount2; //while循环的运行次数
- bool m_isStop; //线程停止标志位
- QMutex m_stopMutex;
- void msleep(unsigned int msec); //线程休眠函数
- };
复制代码
4、创建一个ThreadObject.cpp文件,ThreadObject类里面的成员函数,均在这个文件里面实现。代码如下所示:
- //线程的耗时操作都在这里进行
- void ThreadObject::runSomeBigWork1()
- {
- {
- QMutexLocker locker(&m_stopMutex);
- m_isStop = false;
- }
- int count = 0;
- QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());
- emit message(str);
- int process = 0;
- emit progress(process);
- while(1)
- {
- {
- QMutexLocker locker(&m_stopMutex);
- if(m_isStop) //通过m_isStop变量来退出线程
- return;
- }
- int pro = ((float)count / m_runCount) * 100;
- if(pro != process)
- {
- process = pro;
- emit progress(((float)count / m_runCount) * 100); //线程运行的百分比
- emit message(QString("Object::run times:%1,m_runCount:%2").arg(count).arg(m_runCount));
- }
- msleep(1000);
- count++;
- if(m_runCount < count) //线程达到最大的运行次数,则退出
- {
- break;
- }
- }
- }
- //线程的耗时操作都在这里进行
- void ThreadObject::runSomeBigWork2()
- {
- {
- QMutexLocker locker(&m_stopMutex);
- m_isStop = false;
- }
- int count = 0;
- QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());
- emit message(str);
- int process = 0;
- QElapsedTimer timer;
- timer.start();
- while(1)
- {
- {
- QMutexLocker locker(&m_stopMutex);
- if(m_isStop)
- return;
- }
- int pro = ((float)count / m_runCount2) * 100;
- if(pro != process)
- {
- process = pro;
- emit progress(pro);
- emit message(QString("%1,%2,%3,%4").arg(count).arg(m_runCount2).arg(pro).arg(timer.elapsed()));
- timer.restart();
- }
- msleep(1000);
- count++;
- if(m_runCount2 < count) //线程达到最大的运行次数,则退出
- {
- break;
- }
- }
- }
复制代码
线程的耗时操作都在这两个函数中进行,这两个函数都是通过ui线程中的信号进行触发的。同时,为了对线程进行停止,这里使用了一个m_isStop的成员变量。
通过在ui线程中,调用stop()函数修改m_isStop变量,达到退出线程的目的。注意,修改m_isStop变量时,要进行互斥锁操作。
5、在widget.cpp文件中,ui窗口进行构造时,先设置进度条的初始状态,并关联定时器的超时槽函数,这个定时器用来监控ui线程是否卡死。代码如下所示:
- Widget::Widget(QWidget *parent) :
- QWidget(parent),
- ui(new Ui::Widget)
- {
- ui->setupUi(this);
- m_obj = NULL;
- m_objThread = NULL;
- ui->progressBar->setRange(0,100);
- ui->progressBar->setValue(0);
- ui->progressBar_heart->setRange(0,100);
- ui->progressBar_heart->setValue(0);
- //启动一个定时器,用来监视ui线程是否有卡死
- connect(&m_heart,SIGNAL(timeout()),this,SLOT(heartTimeOut()));
- m_heart.setInterval(100);
- m_heart.start(); //启动定时器,不断更新heartbeat进度条
- //打印出 ui 的线程id
- receiveMessage(QString("%1->%2,current ui id is:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));
- }
复制代码
6、点击 [moveToThread run 1] 或 [moveToThread run 2] 按钮,即可调用startObjThread()启动线程,然后通过发送信号,唤起线程耗时操作的槽函数。startObjThread()函数的具体实现如下所示:
- //使用moveToThread的方式启动一个线程
- void Widget::startObjThread()
- {
- if(m_objThread) //如果这个线程已经存在,则不再启动
- {
- return;
- }
- m_objThread= new QThread(); //创建一个QThread对象
- m_obj = new ThreadObject(); //使用继承于QObject的类创建一个对象
- m_obj->moveToThread(m_objThread); //把继承于QObject的类对象转移到新的线程运行
- //关联deleteLater槽函数,这是线程安全退出的关键
- connect(m_objThread,SIGNAL(finished()),m_objThread,SLOT(deleteLater()));
- connect(m_objThread,SIGNAL(finished()),m_obj,SLOT(deleteLater()));
- //关联两个信号,用于启动线程里面的耗时操作
- connect(this,SIGNAL(startObjThreadWork1()),m_obj,SLOT(runSomeBigWork1()));
- connect(this,SIGNAL(startObjThreadWork2()),m_obj,SLOT(runSomeBigWork2()));
- //关联两个信号,用于更新进度条和打印信息
- connect(m_obj,SIGNAL(progress(int)),this,SLOT(progress(int)));
- connect(m_obj,SIGNAL(message(QString)),this,SLOT(receiveMessage(QString)));
- m_objThread->start();
- }
复制代码
这里需要注意,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周年了!感谢大家的支持与爱护!!
曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……
|