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

本文详述了在QT中使用继承QObject而非QThread来实现多线程的方法,包括线程创建、信号槽机制、资源安全释放及线程同步等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

https://blog.youkuaiyun.com/czyt1988/article/details/71194457

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

关于上一篇文章,请点击这里。https://blog.youkuaiyun.com/wenjs0620/article/details/89331788

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线程的。

 

点击这里,下载源码

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

工程师进阶笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值