Qt5教程: (9) Qt多線程


0. 創建工程

先創建一個工程吧, 具體步驟前面講過很多次了, 就不再細說了。

然后在Header文件夾下創建添加一個頭文件, 右鍵Headers -> Add New... -> C++ -> C++ Header File -> Choose

隨便起個名字, 比如mythread, 然后點Next->Finish。

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()為虛函數, 需要我們來重載)。

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

    • 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);

    • 用於設置正在運行的線程的優先級, 如果線程未運行, 則該返回不會執行任何操作並立刻返回。可用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();
}

運行結果如下:


此文原創禁止轉載,轉載文章請聯系博主並注明來源和出處,謝謝!
作者: Raina_RLN https://www.cnblogs.com/raina/


免責聲明!

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



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