QT線程的結束


原貼地址:https://blog.csdn.net/caoshangpa/article/details/62421334

感謝原作者的幫助!

如果一個線程運行完成,就會結束。可很多情況並非這么簡單,由於某種特殊原因,當線程還未執行完時,我們就想中止它。
不恰當的中止往往會引起一些未知錯誤。比如:當關閉主界面的時候,很有可能次線程正在運行,這時,就會出現如下提示:
QThread: Destroyed while thread is still running
這是因為次線程還在運行,就結束了UI主線程,導致事件循環結束。這個問題在使用線程的過程中經常遇到,尤其是耗時操作。
在此問題上,常見的兩種人:
1.直接忽略此問題。
2.強制中止 - terminate()。
大多數情況下,當程序退出時,次線程也許會正常退出。這時,雖然抱着僥幸心理,但隱患依然存在,也許在極少數情況下,就會出現Crash。
正如前面提到過terminate(),比較危險,不鼓勵使用。線程可以在代碼執行的任何點被終止。線程可能在更新數據時被終止,從而沒有機會來清理自己,解鎖等等。。。總之,只有在絕對必要時使用此函數。
所以,我們應該采取合理的措施來優雅地結束線程,一般思路:
1.發起線程退出操作,調用quit()或exit()。
2.等待線程完全停止,刪除創建在堆上的對象。
3.適當的使用wait()(用於等待線程的退出)和合理的算法。
下面介紹兩種方式:
一.QMutex互斥鎖 + bool成員變量。
這種方式是Qt4.x中比較常用的,主要是利用“QMutex互斥鎖 + bool成員變量”的方式來保證共享數據的安全性(可以完全參照下面的requestInterruption()源碼寫法)。

#include <QThread>
#include <QMutexLocker>
 
class WorkerThread : public QThread
{
    Q_OBJECT
 
public:
    explicit WorkerThread(QObject *parent = 0)
        : QThread(parent),
          m_bStopped(false)
    {
        qDebug() << "Worker Thread : " << QThread::currentThreadId();
    }
 
    ~WorkerThread()
    {
        stop();
        quit();
        wait();
    }
 
    void stop()
    {
        qDebug() << "Worker Stop Thread : " << QThread::currentThreadId();
        QMutexLocker locker(&m_mutex);
        m_bStopped = true;
    }
 
protected:
    virtual void run() Q_DECL_OVERRIDE {
        qDebug() << "Worker Run Thread : " << QThread::currentThreadId();
        int nValue = 0;
        while (nValue < 100)
        {
            // 休眠50毫秒
            msleep(50);
            ++nValue;
 
            // 准備更新
            emit resultReady(nValue);
 
            // 檢測是否停止
            {
                QMutexLocker locker(&m_mutex);
                if (m_bStopped)
                    break;
            }
            // locker超出范圍並釋放互斥鎖
        }
    }
signals:
    void resultReady(int value);
 
private:
    bool m_bStopped;
    QMutex m_mutex;
};

為什么要加鎖?很簡單,是為了共享數據段操作的互斥。
何時需要加鎖?在形成資源競爭的時候,也就是說,多個線程有可能訪問同一共享資源的時候。
當主線程調用stop()更新m_bStopped的時候,run()函數也極有可能正在訪問它(這時,他們處於不同的線程),所以存在資源競爭,因此需要加鎖,保證共享數據的安全性。

二.Qt5以后:requestInterruption() + isInterruptionRequested()
這兩個接口是Qt5.x引入的,使用很方便:

class WorkerThread : public QThread
{
    Q_OBJECT
 
public:
    explicit WorkerThread(QObject *parent = 0)
        : QThread(parent)
    {
    }
 
    ~WorkerThread() {
        // 請求終止
        requestInterruption();
        quit();
        wait();
    }
 
protected:
    virtual void run() Q_DECL_OVERRIDE {
        // 是否請求終止
        while (!isInterruptionRequested())
        {
            // 耗時操作
        }
    }
};

在耗時操作中使用isInterruptionRequested()來判斷是否請求終止線程,如果沒有,則一直運行;當希望終止線程的時候,調用requestInterruption()即可。
正如侯捷所言:「源碼面前,了無秘密」。如果還心存疑慮,我們不妨來看看requestInterruption()、isInterruptionRequested()的源碼:

void QThread::requestInterruption()
{
    Q_D(QThread);
    QMutexLocker locker(&d->mutex);
    if (!d->running || d->finished || d->isInFinish)
        return;
    if (this == QCoreApplicationPrivate::theMainThread) {
        qWarning("QThread::requestInterruption has no effect on the main thread");
        return;
    }
    d->interruptionRequested = true;
}
 
bool QThread::isInterruptionRequested() const
{
    Q_D(const QThread);
    QMutexLocker locker(&d->mutex);
    if (!d->running || d->finished || d->isInFinish)
        return false;
    return d->interruptionRequested;
}

補充1:

對於wait並不提倡使用,為什么?因為在不滿足return的條件之前會一直阻塞線程。並且qt是基於事件框架的,所以建議使用偵聽finished信號來替代wait

補充2:

想補充一些,今天項目遇到的,就第一種方法來說,用不用鎖可以看情況,不一定必須要使用,因為在whlie里面加鎖,有時候會因為你線程一直搶到鎖導致你的stop並沒有用。尤其是你很多時候會在whlie里面加continue的情況==。如果只是主線程和這一個線程使用到stop,那么完全可以不用鎖,你最壞的情況就是while會多跑一次。就看這一次對於你的影響大還是長時間得不到鎖的影響大。就比如這段代碼==天坑,鬼知道我經歷了什么

while( true )
  {
 
   int iFailureCount = 0;
   {
    //stop process
    QMutexLocker locker1( &m_stReleaseMutex );
    if(m_bStopProcess)
     break;
    QMutexLocker locker2( m_pSync );
    if( m_listTextArray.isEmpty())
    {
#ifdef PROC_EFFI_TEST
     {
      itime = t.restart();
      out &lt;&lt;  atime &lt;&lt; "\n" ;
     }
#endif
     break;
     //continue;
    }
    
 
       m_listTextArrayPicked = m_listTextArray;
    listTextArray2 = m_listTextArrayPicked;
    m_listTextArray.clear();
 
   }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM