嵌入式Linux應用程序開發-(6)嵌入式QT多線程的簡單實現(方法二)

嵌入式QT多線程的簡單實現(方法二)

本文的內容是拜讀完以下文章後的總結,喝水不忘挖井人,感謝前輩的肩膀,讓我們這些晚輩少走彎路,走得更遠。如果已經理解了原作者的文章,則可完全忽略本文,感謝支持和關注。

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

上一篇文章介紹了使用繼承QThread類,重載run()函數的方法來實現多線程,這種方法是QT實現多線程編程的傳統方法,但從QT4.8以後的版本開始,QT官方並不是很推薦使用這種方法,而是極力推薦使用繼承QObject類的方法來實現多線程,因爲QObject類比QThread類更加靈活。當然,使用哪種方法實現多線程,需要根據具體的實際情況而定。

關於上一篇文章,請點擊這裏。https://blog.csdn.net/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線程的。

 

點擊這裏,下載源碼

點擊這裏,瞭解更多Linux開發板信息

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章