Qt5教程: (9) Qt多線程

0. 建立工程

先建立一個工程吧, 具體步驟前面講過不少次了, 就再也不細說了。框架

而後在Header文件夾下建立添加一個頭文件, 右鍵Headers -> Add New... -> C++ -> C++ Header File -> Choose函數

隨便起個名字, 好比mythread, 而後點Next->Finish。ui

1. QThread 源碼一覽

mythread.h中包含QThread頭文件:操作系統

按住Ctrl鍵, 點擊QThread, 再按住Ctrl鍵點擊qthread.h進入到qthread.h文件, 源碼就在這裏了, 隨便看看就好。哪裏不懂就鼠標點一下不懂的地方, 而後按F1, 會跳轉到相應的幫助文檔,裏面講得很詳細, 裏面的英文也比較簡單。線程

2. QThread相關方法介紹

2.1 啓動線程

  • void start(Priority = InheritPriority);
    • 經過調用start()方法來啓動線程,該方法會調用run()函數(能夠看到QThread中run()爲虛函數, 須要咱們來重載)。code

    • run()函數可調用exec()讓該線程進入事件循環。orm

    • Priority爲線程優先級(下面會講)。對象

2.2 關閉線程

  • void exit(int retcode = 0);
    • 使線程退出事件循環, 若是該線程沒有事件循環, 不作任何操做。
    • retcode默認爲0, 表示正常返回。而非0值表示異常退出。
  • void quit();
    • 至關於exit(0)
  • void terminate();
    • 由操做系統強行終止該線程, 可能會致使沒法完成一些清理工做, 不推薦使用。
  • void requestInterruption(); + bool isInterruptionRequested();
    • Qt5的新接口, requestInterruption用於請求線程進行中斷。isInterruptionRequested返回true/false, 用於判斷是否有終止線程的請求。

2.3 阻塞線程

  • bool wait(unsigned long time = ULONG_MAX);
    • 阻塞線程time毫秒, 默認永久阻塞;
    • 只有當線程結束(從run函數返回), 或阻塞超時纔會返回;
    • 線程結束或還未啓動, wait返回值爲true, 超時的返回值爲false。
  • static void sleep(unsigned long);
    • 阻塞xx秒, 無返回值。
  • static void msleep(unsigned long);
    • 阻塞xx毫秒, 無返回值。
  • static void usleep(unsigned long);
    • 阻塞xx微秒, 無返回值。

2.4線程狀態判斷

  • bool isFinished() const;
    • 若是線程結束返回true, 不然返回false。
  • bool isRunning() const;
    • 若是線程正在運行返回true, 不然返回false。
  • bool isInterruptionRequested() const;
    • 若是有終止線程的請求返回true, 不然返回false; 請求可由requestInterruption()發出。

2.5 設置優先級

  • void setPriority(Priority priority);blog

    • 用於設置正在運行的線程的優先級, 若是線程未運行, 則該返回不會執行任何操做並馬上返回。可用start(priority)啓動帶優先級的線程。

    • 指定的優先級是否生效取決於操做系統的調度, 若是是不支持線程優先級的系統上, 優先級的設置將被忽略。

    • 優先級能夠設置爲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 使用和建立線程一樣的優先級(這是默認值)

2.6 信號

  • void started(QPrivateSignal);
    • 在線程start後, 執行run前發出該信號。
  • void finished(QPrivateSignal);
    • 在線程結束, 徹底退出前發送此信號。

3. 建立線程

3.1 繼承QThread方式

a. 定義MyThread類

mythread.h中定義MyThread類, 並繼承QThread, 而後把框架寫好:

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT

public:
    MyThread();

private:

protected:
    void run();

signals:

public slots:


};

#endif // MYTHREAD_H

b. 重載run()

新建一個C++ Source File, 命名爲mythread.cpp

mythread.cpp代碼以下, run()函數中咱們讓它每隔1秒打印一次字符串:

#include "mythread.h"

// 構造函數
MyThread::MyThread()
{

}

void MyThread::run()
{
    while (!isInterruptionRequested())
    {
        qDebug() << "Running...";
        sleep(1);
    }

    qDebug() << "Get Interruption Request, I'll exit.";
}

由於用到了qDebug(), 別忘了在mythread.h中添加<QDebug>頭文件:

#include <QDebug>

c. 開始和結束線程

mainwindow.h中添加頭文件和聲明變量:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "mythread.h"  // 添加頭文件

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    MyThread *my_thread;  // 聲明變量
};

#endif // MAINWINDOW_H

mainwindow.cpp中開啓和結束線程:

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    my_thread = new MyThread;  // 實例化
    my_thread->start();  // 開啓線程


    // 主線程阻塞5秒
    QDateTime start = QDateTime::currentDateTime();
    QDateTime now;
    do {
        now = QDateTime::currentDateTime();
    } while(start.secsTo(now) < 5);


    // 關閉線程
    my_thread->requestInterruption();
    my_thread->wait();

}

MainWindow::~MainWindow()
{

}

由於用到了<QDateTime>, 別忘了在mainwindow.h中添加頭文件:

#include <QDateTime>

運行結果:

能夠看到主線程被阻塞了5秒, 以後才彈出窗口。可是在主線程阻塞期間, 咱們的my_thread線程仍在運行, 直到線程被關閉。

附: Qt4適用寫法

上面咱們結束線程使用的是requestInterruption()isInterruptionRequested(), 這是Qt5新增的, 那麼Qt4要如何結束線程呢?

  • 首先須要使用一個flag來標識線程的狀態(執行仍是中止), 好比定義一個變量bool is_stopped 初值賦爲false;
  • 而後本身寫一個結束線程的函數, 好比stop(), 當調用my_thread->stop();時將is_stopped改成true;
  • run()中判斷, 若是is_stoppedfalse線程繼續執行, 若是爲true線程退出; 別忘了退出前再將is_stopped改成false, 否則線程無法再次開啓了。

代碼以下:

mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <QDebug>

class MyThread : public QThread
{
    Q_OBJECT

public:
    MyThread();
    void stop();  // 添加stop()方法

private:
    volatile bool is_stopped;  // 添加標識變量

protected:
    void run();

signals:

public slots:


};

#endif // MYTHREAD_H

mythread.cpp

#include "mythread.h"

// 構造函數
MyThread::MyThread()
{
    is_stopped = false;  // 初始化標識變量
}

void MyThread::run()
{
    while (!is_stopped)  // 更改判斷條件
    {
        qDebug() << "Running...";
        sleep(1);
    }

    qDebug() << "is_stopped is true, I'll exit.";
    is_stopped = false;  // 重置變量值
}

// 關閉線程
void MyThread::stop()
{
    is_stopped = true;
}

mainwindow.cpp

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    my_thread = new MyThread;  // 實例化
    my_thread->start();  // 開啓線程


    // 主線程阻塞5秒
    QDateTime start = QDateTime::currentDateTime();
    QDateTime now;
    do {
        now = QDateTime::currentDateTime();
    } while(start.secsTo(now) < 5);


    // 關閉線程
    my_thread->stop();  // 用本身定義的方法關閉線程
    my_thread->wait();

}

MainWindow::~MainWindow()
{

}

附: exit()和requestInterruption()區別

看例子, 咱們修改一下run()函數和關閉線程部分的代碼:

mythread.cpp

void MyThread::run()
{
    while (!isInterruptionRequested())
    {
        qDebug() << "Running...";
        sleep(1);
    }
    qDebug() << "子線程: 我只退出了while循環, 沒有真正結束";

    exec();  // 事件循環
    qDebug() << "子線程: 我真的要結束了";
}

mainwindow.cpp

// 關閉線程
    my_thread->requestInterruption();
    qDebug() << "主線程: 發起中斷請求";
    my_thread->wait(3000);

    my_thread->quit();
    qDebug() << "主線程: 請求退出線程的事件循環";
    my_thread->wait();  // 等待線程結束

運行結果:

在主進程requestInterruption()後, 只是使得isInterruptionRequested()變爲true, 退出了while循環, 在主線程中調用wait(3000), 並無馬上返回, 而是3秒超時後才返回, 說明子線程沒有真正結束, 而是執行到了exec()處進行事件循環。經過調用quit()exit()來結束子線程的事件循環, 子線程才真的結束了。

3.2 moveToThread方式(Qt5新增 官方推薦)

a. 定義一個繼承QObject的類

  • 首先, 建立一個類並繼承QObject, 把要在線程中執行的工做做爲類的槽函數:

dowork.h

#ifndef DOWORK_H
#define DOWORK_H

#include <QObject>
#include <QDateTime>
#include <QDebug>

class DoWork : public QObject
{
    Q_OBJECT
public:
    explicit DoWork(QObject *parent = nullptr);

public slots:
    void do_something();
};

#endif // DOWORK_H

dowork.cpp

#include "dowork.h"

DoWork::DoWork(QObject *parent) : QObject(parent)
{

}

void DoWork::do_something()
{
    int a = 5;
    while(a--)
    {
        qDebug() << "Doing something ...";
        QDateTime start = QDateTime::currentDateTime();
        QDateTime now;
        do {
            now = QDateTime::currentDateTime();
        } while(start.secsTo(now) < 1);
    }
    qDebug() << "Done";
}

b. moveToThread

  • 而後, 建立一個線程對象, 把work1對象移到新線程下:

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QThread>
#include "dowork.h"

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    DoWork *work1;  // 自定義的類
    QThread *new_thread;  // 新線程
};

#endif // MAINWINDOW_H

mainwindow.cpp

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    // 實例化
    work1 = new DoWork;
    new_thread = new QThread;
    
    work1->moveToThread(new_thread);  // 搬到線程下
}

c. 啓動線程

  • 綁定線程啓動後要作的工做:

    connect(new_thread, &QThread::started, work1, &DoWork::do_something);

    使用moveToThread的方法很是靈活, 你不必定要用&QThread::started來觸發do_something, 也可使用自定義的信號, 爲了例程簡單明瞭, 這裏不舉例了。

  • 啓動線程

    new_thread->start();

d. 結束後的清理工做

  • 爲了更安全, 線程結束後別忘了釋放資源:

    connect(new_thread, &QThread::finished, work1, &QObject::deleteLater);
    MainWindow::~MainWindow()
    {
        new_thread->requestInterruption();
        new_thread->quit();
        new_thread->wait();
    }

附: mainwindow.cpp 完整代碼

mainwindow.cpp

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    // 實例化
    work1 = new DoWork;
    new_thread = new QThread;

    work1->moveToThread(new_thread);  // 搬到線程下

    connect(new_thread, &QThread::started, work1, &DoWork::do_something);
    connect(new_thread, &QThread::finished, work1, &QObject::deleteLater);

    new_thread->start();
}

MainWindow::~MainWindow()
{
    new_thread->requestInterruption();
    new_thread->quit();
    new_thread->wait();
}

運行結果以下:

相關文章
相關標籤/搜索