Qt 進程和線程之二:啟動線程的兩種方式


Qt提供了一個與平台無關的QThread類,用以對線程的支持。多線程編程也可以有效解決在不凍結一個應用程序的用戶界面情況下執行一個耗時的操作問題。對應本節的內容,可以在幫助中査看Thread Support in Qt關鍵字。

這里准備介紹QThread常用函數和啟動線程的兩種方式:

  • 子類化
  • 使用worker-object通過QObject::moveToThread將它們移動到線程中

一、QThread常用函數

可以將常用的函數按照功能進行以下分類:

  • 線程啟動

    • void start()

      調用后會執行run()函數,但在run()函數執行前會發射信號started(),操作系統將根據優先級參數調度線程。如果線程已經在運行,那么這個函數什么也不做。優先級參數的效果取決於操作系統的調度策略。

  • 線程執行

    • int exec()

      每一個線程可以有自己的事件循環,可以通過調用exec()函數來啟動事件循環。

    • void run()

      線程的起點,在調用start()之后,新創建的線程就會調用這個函數,默認實現調用exec(),大多數需要重新實現這個函數,便於管理自己的線程。該函數返回后,線程便執行結束,就像應用程序離開main()函數一樣。

  • 線程退出

    • void quit()

      使線程退出事件循環,返回0表示成功,相當於調用了QThread::exit(0)。

    • void exit(int returnCode = 0)

      使線程退出事件循環,返回0表示成功,任何非0值表示失敗。

    • void terminate()

      在極端情況下,可能想要強制終止一個正在執行的線程,這時可以使用terminate()函數。但是,線程可能會立即被終止也可能不會,這取決於操作系統的調度策略,使用terminate()之后再使用QThread::wait(),以確保萬無一失。

      警告:使用terminate()函數,線程可能在任何時刻被終止而無法進行一些淸理工作,因此該函數是很危險的,一般不建議使用,只有在絕對必要的時候使用。

    • void requestInterruption()

      Qt5新引入接口,請求線程的中斷,用於關閉線程。該請求是咨詢意見並且取決於線程上運行的代碼,來決定是否及如何執行這樣的請求。此函數不停止線程上運行的任何事件循環,並且在任何情況下都不會終止它。

  • 線程等待

    • void msleep(unsigned long msecs) [static]

      強制當前線程睡眠msecs毫秒。

    • void sleep(unsigned long secs) [static]

      強制當前線程睡眠secs秒。

    • void usleep(unsigned long usecs) [static]

      強制當前線程睡眠usecs微秒。

    • bool wait(unsigned long time = ULONG_MAX)

      線程將會被阻塞,等待time毫秒。和sleep不同的是,如果線程退出,wait會返回。

  • 線程狀態

    • bool isFinished() const

      判斷線程是否結束

    • bool isRunning() const

      判斷線程是否正在運行

    • bool isInterruptionRequested() const
      如果線程上的任務運行應該停止,返回true;可以使用requestInterruption()請求中斷。 Qt5新引入接口,用於使長時間運行的任務干凈地中斷。從不檢查或作用於該函數返回值是安全的,但是建議在長時間運行的函數中經常這樣做。注意:不要過於頻繁調用,以保持較低的開銷。示例程序如下:

      void run() 
      {
      	// 是否請求終止
      	while (!isInterruptionRequested())
      	{
      		// 耗時操作
      	}
      }
      
  • 線程優先級

    • void setPriority(Priority priority)

      設置正在運行線程的優先級。如果線程沒有運行,此函數不執行任何操作並立即返回。使用的start()來啟動一個線程具有特定的優先級。優先級參數可以是QThread::Priority枚舉除InheritPriortyd的任何值。


枚舉QThread::Priority:

常量 描述
QThread::IdlePriority 0 沒有其它線程運行時才調度
QThread::LowestPriority 1 比LowPriority調度頻率低
QThread::LowPriority 2 比NormalPriority調度頻率低
QThread::NormalPriority 3 操作系統的默認優先級
QThread::HighPriority 4 比NormalPriority調度頻繁
QThread::HighestPriority 5 比HighPriority調度頻繁
QThread::TimeCriticalPriority 6 盡可能頻繁的調度
QThread::InheritPriority 7 使用和創建線程同樣的優先級. 這是默認值

二、子類化QThread方式啟動線程

一個QThread代表了一個在應用程序中可以獨立控制的線程,它與進程中的其他線程分享數據,但是是獨立執行的。相對於一般的程序都是從main()函數開始執行,QThread從main()函數開始執行。默認的,run()通過調用exec()來開啟事件循環。要創建一個線程,需要子類化QThread並且重新實現run()函數。例如:

class MyThread : public QThread
{
protected:
    void run();
};

void MyThread::run()
{
    QTcpSocket socket;
	...
    socket connectToHost(hostName, portNumber);
    exec();
}

這樣會在一個線程中創建一個QTcpSocket,然后執行這個線程的事件循環。可以在外部創建該線程的實例,然后調用start()函數來開始執行該線程,start()默認會調用run()函數。當從run()函數返回后,線程便執行結束。

注意,在線程中是無法使用任何的部件類的。


實例程序

下面來看一個在圖形界面程序中啟動一個線程的例子,在界面上有兩個按鈕,一個用於開啟一個線程,一個用於關閉該線程。新建Qt Gui應用,名稱為myThread,類名為Dialog,基類選擇QDialog。完成后進入設計模式,向界面中放人兩個Push Button按鈕,將第一個按鈕的顯示文本更改為“啟動線程”,名稱更改為startButton;將第二個按鈕的顯示文本更改為 “終止線程”,名稱更改為stopButton,將其enabled屬性取消選中。然后向項目中添加新的C++類,類名設置為“MyThread”,基類設置為“QThread”,類型信息選擇“繼承自QObject”。完成后進入mythread.h文件,修改如下:

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
    void stop();

protected:
    void run();

private:
    volatile bool stopped;
};

#endif // MYTHREAD_H

這里stopped變量使用了volatile關鍵字,這樣可以使它在任何時候都保持最新的值,從而可以避免在多個線程中訪問它時出錯。然后進入mythread.cpp文件中,修改如下:

#include "mythread.h"
#include <QDebug>

MyThread::MyThread(QObject *parent) :
    QThread(parent)
{
    stopped = false;
}

void MyThread::run()
{
    qreal i = 0;
    while (!stopped) 
    {
        qDebug() << QString("in MyThread: %1").arg(i);
        msleep(1000);
        i++;
    }
    stopped = false;
}

void MyThread::stop()
{
    stopped = true;
}

(1)構造函數中將stopped變量初始化為false。

(2)run()函數中一直判斷stopped變量的值,只要它為false,那么每過1秒就一直打印i值遞增的字符串。

(3)top()函數中將stopped變量設置為了true,這樣便可以結束run()函數中的循環,從而從run()函數中退出,這樣整個線程也就結束了。這里使用了stopped變量來實現了進程的終止,並沒有使用危險的terminate()函數,也沒有在析構函數中使用quit()、wait()和requestInterruption()函數。


下面在Dialog類中使用自定義的線程。先到dialog.h文件中,添加頭文件"mythread.h",然后添加私有對象:

#include "mythread.h"
...
private:    
	MyThread thread;

下面到設計模式,分別進入兩個按鈕的單擊信號對應的槽,更改如下:

// 啟動線程按鈕
void Dialog::on_startButton_clicked()
{
    thread.start();
    ui->startButton->setEnabled(false);
    ui->stopButton->setEnabled(true);
}

// 終止線程按鈕
void Dialog::on_stopButton_clicked()
{
    if (thread.isRunning())
    {
        thread.stop();
        ui->startButton->setEnabled(true);
        ui->stopButton->setEnabled(false);
    }
}

啟動線程時調用了start()函數,然后設置了兩個按鈕的狀態。在終止線程時, 先使用isRunning()來判斷線程是否在運行,如果是,則調用stop()函數來終止線程,並且更改兩個按鈕的狀態。現在運行程序,單擊“啟動線程”按鈕,査看應用程序輸出欄的輸出,然后再按下“終止線程”按鈕,可以看到已經停止輸出了。


三、worker-object方式啟動線程

還有一種創建線程的方法,就是使用worker-object通過QObject::moveToThread將它們移動到線程中。例如:

//Worker類:在線程中執行的類,例如定時器超時操作
class Worker類 : public QObject
{
    Q_OBJECT

public slots:
    void doWork(const QString &parameter) 
    {
        QString result;
        // 這里是阻塞的操作
        emit resultReady(result);
    }

signals:
    void resultReady(const QString &result);
};

//Controller類:線程所在的類
class Controller : public QObject
{
    Q_OBJECT
    QThread workerThread;
    
public:
    Controller() 
    {
        Worker *worker = new Worker;
        //將worker對象的線程從主線程移動到workerThread
        worker->moveToThread(&workerThread);
        //當workerThread線程結束后,會自動銷毀該線程
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        
        //當Controller類發射了operate()信號,會調用worker對象的doWork槽函數
        connect(this, &Controller::operate, worker, &Worker::doWork);
        //當workerr類發射了resultReady()信號,會調用Controller對象的handleResults槽函數
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        
        //最開始,啟動workerThread線程
        workerThread.start();
    }
    
    ~Controller() 
    {
    	//在析構函數中終止進程
    	workerThread.requestInterruption();
        workerThread.quit();
        workerThread.wait();
    }
    
public slots:
    void handleResults(const QString &);
    
signals:
    void operate(const QString &);
};

這樣Worker槽中的代碼將在一個單獨的線程中執行,使用這種方式可以很容易地將一些費時的操作放到單獨的工作線程中來完成。可以將任意線程中任意對象的任意一個信號關聯到Worker的槽上,不同線程間的信號和槽進行關聯是安全的。

注:另外有時間可以參考Qt——線程與定時器這篇博客進一步了解啟動線程的這兩種方式。


四、關閉線程

關閉線程有多種方法,這里介紹一種最常用的方法:在析構函數中使用quit()、wait()和requestInterruption()函數。示例程序如下:

~WorkerThread() 
{
	// 請求終止
	requestInterruption();
	quit();
	wait();
}

參考:

Qt 之 QThread



免責聲明!

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



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