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, 用於判斷是否有終止線程的請求。
- Qt5的新接口,
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()
發出。
- 如果有終止線程的請求返回true, 否則返回false; 請求可由
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_stopped
為false
線程繼續執行, 如果為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/