在Qt中,可以透過以下幾種常見方式來啟動執行緒

MarsCactus發表於2024-11-10

在Qt中,可以透過以下幾種常見方式來啟動執行緒:

1. 繼承QThread類並重寫run()方法

  • 步驟一:建立自定義執行緒類
    • 首先,需要從QThread類繼承建立一個自定義的執行緒類。例如:
#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
    ~MyThread();

protected:
    void run() override;
};

MyThread::MyThread(QObject *parent) : QThread(parent)
{
}

MyThread::~MyThread()
{
}

void MyThread::run()
{
    // 在這裡放置執行緒要執行的任務程式碼
    // 例如,可以是一個長時間執行的計算任務、檔案讀取任務等
    qDebug() << "執行緒正在執行任務";
}
  • 步驟二:建立執行緒物件並啟動執行緒
    • 在需要啟動執行緒的地方,建立自定義執行緒類的物件,然後呼叫start()方法來啟動執行緒。例如:
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MyThread *thread = new MyThread();
    thread->start();

    return a.exec();
}
- 當呼叫`start()`方法後,執行緒會進入就緒狀態,一旦系統資源允許,就會開始執行`run()`方法中的程式碼,從而啟動執行緒執行相應的任務。

2. 使用QThreadPool和QRunnable

  • 步驟一:建立可執行物件類(繼承QRunnable)
    • 建立一個繼承自QRunnable的類,在這個類中定義執行緒要執行的任務。例如:
#include <QThreadPool>
#include <QRunnable>

class MyRunnable : public QRunnable
{
public:
    MyRunnable();

    void run() override;
};

MyRunnable::MyRunnable()
{
}

void MyRunnable::run()
{
    // 在這裡放置執行緒要執行的任務程式碼
    qDebug() << "透過QThreadPool啟動的執行緒正在執行任務";
}
  • 步驟二:使用QThreadPool來排程執行緒執行任務
    • 在主程式中,先建立QThreadPool物件,然後將可執行物件類的例項新增到執行緒池中,由執行緒池來排程執行緒執行任務。例如:
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QThreadPool pool;
    MyRunnable *runnable = new MyRunnable();
    pool.start(runnable);

    // 可以根據需要設定執行緒池的一些屬性,比如最大執行緒數等
    pool.setMaxThreadCount(5);

    return a.exec();
}
- QThreadPool會根據系統資源和自身的排程策略,安排可執行物件類中的任務在合適的執行緒中執行。這種方式適合處理大量的、相對獨立的小任務,可以更有效地利用系統資源,並且便於管理執行緒的數量和執行情況。

3. 使用QtConcurrent::run()函式

  • 步驟一:定義要線上程中執行的函式
    • 首先需要定義一個普通函式,這個函式將作為執行緒要執行的任務。例如:
#include <QtConcurrent>

void myFunction()
{
    // 在這裡放置執行緒要執行的任務程式碼
    qDebug() << "透過QtConcurrent::run()啟動的執行緒正在執行任務";
}
  • 步驟二:使用QtConcurrent::run()啟動執行緒執行任務
    • 在主程式中,使用QtConcurrent::run()函式來啟動執行緒執行定義好的函式任務。例如:
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QtConcurrent::run(myFunction);

    return a.exec();
}
- QtConcurrent::run()函式會自動建立一個執行緒來執行指定的函式任務,並且會根據系統資源情況進行合理的排程。這種方式相對簡單快捷,適合啟動單個執行緒來執行一些簡單的任務,不需要建立專門的執行緒類或可執行物件類。

以上就是在Qt中啟動執行緒的幾種常見方式,每種方式都有其特點和適用場景,可以根據具體的專案需求來選擇合適的執行緒啟動方式。

=========================================================

以下是一個更詳細的使用QThreadPool和QRunnable啟動執行緒的程式碼示例,展示瞭如何在實際場景中利用它們來執行多個任務:

#include <QCoreApplication>
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>

// 自定義可執行物件類,繼承自QRunnable
class MyRunnable : public QRunnable
{
public:
    MyRunnable(int taskId) : m_taskId(taskId) {}

    void run() override
    {
        // 模擬執行緒執行一個任務,這裡簡單列印任務資訊
        qDebug() << "執行緒 " << QThread::currentThreadId() << " 正在執行任務 " << m_taskId;

        // 可以在這裡新增實際的任務程式碼,比如長時間執行的計算、檔案讀取等操作

        // 模擬任務執行時間
        QThread::sleep(2);

        qDebug() << "執行緒 " << QThread::currentThreadId() << " 完成任務 " << m_taskId;
    }

private:
    int m_taskId;
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 建立執行緒池物件
    QThreadPool pool;

    // 設定執行緒池的最大執行緒數
    pool.setMaxThreadCount(3);

    // 建立多個可執行物件例項並新增到執行緒池中啟動執行緒執行任務
    for (int i = 1; i <= 5; ++i)
    {
        MyRunnable *runnable = new MyRunnable(i);
        pool.start(runnable);
    }

    // 等待所有任務完成
    pool.waitForDone();

    qDebug() << "所有任務已完成,程式即將退出";

    return a.exec();
}

在上述程式碼中:

  1. 首先定義了MyRunnable類,它繼承自QRunnable。在建構函式中接受一個任務編號taskId,用於區分不同的任務。重寫的run方法中定義了每個任務的具體執行邏輯,這裡只是簡單模擬了任務執行情況,包括列印任務開始和完成資訊,以及透過QThread::sleep模擬任務執行時間。

  2. main函式中:

    • 建立了QThreadPool物件pool
    • 設定了執行緒池的最大執行緒數為3,這意味著執行緒池最多同時啟動3個執行緒來執行任務。
    • 透過迴圈建立了5MyRunnable物件例項,並將它們逐個新增到執行緒池中,由執行緒池來排程執行緒執行這些任務。
    • 最後呼叫pool.waitForDone等待所有任務完成,然後列印出所有任務已完成的資訊。

透過這個示例,你可以看到如何使用QThreadPool和QRunnable來有效地管理和啟動多個執行緒執行不同的任務,並且可以根據需要靈活設定執行緒池的相關屬性,如最大執行緒數等。

========================================================================

在使用QThreadPool和QRunnable時,如何獲取執行緒的執行結果?

在使用QThreadPool和QRunnable時,QRunnable本身並沒有直接提供獲取執行緒執行結果的機制,因為它主要關注的是定義可執行的任務。不過,我們可以透過一些額外的手段來獲取執行緒的執行結果,以下是一種常見的實現方式:

1. 使用訊號與槽機制(Signal-Slot Mechanism)

  • 步驟一:修改可執行物件類(MyRunnable)
    • 首先,在繼承自QRunnable的可執行物件類中新增訊號來表示任務完成以及攜帶執行結果(如果有具體結果需要返回的話)。例如,假設我們的任務是進行一個簡單的數學計算並返回結果,可如下修改:
#include <QObject>
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>

class MyRunnable : public QRunnable, public QObject
{
    Q_OBJECT
public:
    MyRunnable(int taskId) : m_taskId(taskId) {}

    void run() override
    {
        // 模擬進行一個簡單的數學計算任務
        int result = m_taskId * 2;

        // 傳送任務完成訊號,並攜帶結果
        emit taskFinished(result);

        qDebug() << "執行緒 " << QThread::currentThreadId() << " 完成任務 " << m_taskId;
    }

signals:
    void taskFinished(int result);

private:
    int m_taskId;
};
- 在上述程式碼中,我們在`MyRunnable`類中新增了`taskFinished`訊號,用於在任務完成時傳送,並攜帶計算得到的結果。
  • 步驟二:在主執行緒中連線訊號與槽
    • 在主執行緒中,建立MyRunnable物件並新增到QThreadPool啟動執行緒執行任務之前,需要連線taskFinished訊號到一個槽函式,以便在任務完成時接收並處理結果。例如:
#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QThreadPool pool;

    // 設定執行緒池的最大執行緒數
    pool.setMaxThreadCount(3);

    // 建立可執行物件例項並連線訊號與槽
    MyRunnable *runnable = new MyRunnable(1);
    QObject::connect(runnable, &MyRunnable::taskFinished, [](int result) {
        qDebug() << "接收到任務結果:" << result;
    });

    // 新增到執行緒池啟動執行緒執行任務
    pool.start(runnable);

    // 等待所有任務完成
    pool.waitForDone();

    qDebug() << "所有任務已完成,程式即將退出";

    return a.exec();
}
- 在上述`main`函式中,我們建立了`MyRunnable`物件`runnable`後,透過`QObject::connect`函式連線了`runnable`的`taskFinished`訊號到一個匿名的槽函式,該槽函式會在接收到任務完成訊號時列印出接收到的結果。

2. 使用共享資料結構(如全域性變數或類成員變數)

  • 步驟一:定義共享資料結構
    • 可以定義一個全域性變數或者在某個類中定義成員變數來作為共享資料結構,用於儲存執行緒執行的結果。例如,定義一個全域性的std::vector來儲存多個任務的結果(假設每個任務都返回一個整數結果):
#include <vector>

std::vector<int> globalResults;
  • 步驟二:在可執行物件類中更新共享資料結構
    • MyRunnable類的run方法中,將任務執行結果儲存到共享資料結構中。以下是修改後的MyRunnable類程式碼:
#include <QObject>
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>

class MyRunnable : public QRunnable, public QObject
{
    Q_OBJECT
public:
    MyRunnable(int taskId) : m_taskId(taskId) {}

    void run() override
    {
        // 模擬進行一個簡單的數學計算任務
        int result = m_taskId * 2;

        // 將結果儲存到全域性變數中
        globalResults.push_back(result);

        qDebug() << "執行緒 " << QThread::currentThreadId() << " 完成任務 " << m_taskId;
    }

private:
    int m_taskId;
};
- 在上述程式碼中,`run`方法在計算出任務結果後,將其新增到`globalResults`全域性變數中。
  • 步驟三:在主執行緒中訪問共享資料結構獲取結果
    • 在主執行緒中,在所有任務透過QThreadPool執行完成後(例如透過呼叫pool.waitForDone),可以直接訪問共享資料結構來獲取執行緒執行的結果。例如:
#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QThreadPool pool;

    // 設定執行緒池的最大執行緒數
    pool.setMaxThreadCount(3);

    // 建立多個可執行物件例項並新增到執行緒池啟動執行緒執行任務
    for (int i = 1; i <= 5; ++i)
    {
        MyRunnable *runnable = new MyRunnable(i);
        pool.start(runnable);
    }

    // 等待所有任務完成
    pool.waitForDone();

    // 訪問全域性變數獲取結果
    for (int result : globalResults)
    {
        qDebug() << "獲取到的任務結果:" << result;
    }

    qDebug() << "所有任務已完成,程式即將退出";

    return a.exec();
}
- 在上述`main`函式中,在所有任務完成後,透過遍歷`globalResults`全域性變數,列印出了每個任務的結果。

需要注意的是,當使用共享資料結構時,要特別注意執行緒安全問題,因為多個執行緒可能會同時訪問和修改這些資料結構。在上述示例中,std::vectorpush_back操作在多執行緒環境下可能會導致資料不一致等問題。為了確保執行緒安全,可以使用一些執行緒安全的容器類(如QVector等)或者在訪問共享資料結構時採取適當的同步措施(如使用互斥鎖等)。

以上就是在使用QThreadPool和QRunnable時獲取執行緒執行結果的兩種常見方法,你可以根據具體的專案需求和場景選擇合適的方式。

===============================================================

除了訊號與槽機制外,以下幾種方法也可用於獲取使用QThreadPool和QRunnable啟動執行緒的執行結果:

1. 利用條件變數(Condition Variable)和互斥鎖(Mutex)

  • 設定共享資料結構
    • 首先定義一個共享的資料結構來儲存執行緒執行的結果。例如,建立一個結構體來儲存任務相關資訊及結果:
#include <mutex>
#include <condition_variable>

struct TaskResult {
    int taskId;
    int result;
    bool isFinished;
};

std::vector<TaskResult> taskResults;
std::mutex mutexResults;
std::condition_variable cv;
- 這裡定義了一個`TaskResult`結構體,包含任務編號`taskId`、任務結果`result`以及一個表示任務是否完成的標誌`isFinished`。同時定義了一個`std::vector`來儲存多個任務的結果,以及互斥鎖`mutexResults`和條件變數`cv`用於保證執行緒安全和實現同步。
  • 在可執行物件類中更新結果
    • 在繼承自QRunnable的可執行物件類(如MyRunnable)的run方法中,執行完任務後更新共享資料結構中的對應任務結果,並設定任務完成標誌。以下是示例程式碼:
class MyRunnable : public QRunnable {
public:
    MyRunnable(int taskId) : m_taskId(taskId) {}

    void run() override {
        // 模擬執行任務,這裡簡單進行一個數學計算
        int result = m_taskId * 2;

        // 獲取互斥鎖
        std::unique_lock<std::mutex> lock(mutexResults);

        // 更新任務結果到共享資料結構
        taskResults.push_back({m_taskId, result, true});

        // 通知等待在條件變數上的執行緒(這裡主要是主執行緒)
        cv.notify_one();

        // 釋放互斥鎖
        lock.unlock();

        qDebug() << "執行緒 " << QThread::currentThreadId() << " 完成任務 " << m_taskId;
    }

private:
    int m_taskId;
};
- 在上述程式碼中,透過互斥鎖保護對共享資料結構`taskResults`的訪問,執行完任務後將結果存入結構體並新增到向量中,然後透過條件變數通知可能正在等待結果的其他執行緒(通常是主執行緒)。
  • 在主執行緒中獲取結果
    • 在主執行緒中,透過迴圈等待條件變數的通知,當收到通知後檢查共享資料結構中任務是否完成,若完成則獲取並處理相應的結果。示例程式碼如下:
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QThreadPool pool;
    pool.setMaxThreadCount(3);

    // 建立多個可執行物件例項並新增到執行緒池啟動執行緒執行任務
    for (int i = 1; i <= 5; ++i) {
        MyRunnable *runnable = new MyRunnable(i);
        pool.start(runnable);
    }

    // 等待所有任務完成並獲取結果
    std::unique_lock<std::mutex> lock(mutexResults);
    while (taskResults.size() < 5) {
        cv.wait(lock);
    }
    lock.unlock();

    // 處理獲取到的結果
    for (const auto& result : taskResults) {
        qDebug() << "獲取到任務 " << result.taskId << " 的結果:" << result.result;
    }

    qDebug() << "所有任務已完成,程式即將退出";

    return a.exec();
}
- 在上述`main`函式中,主執行緒透過`cv.wait`等待條件變數的通知,一旦收到通知且滿足所有任務結果都已獲取的條件(透過檢查`taskResults`的大小),就可以解鎖互斥鎖並處理獲取到的結果。

2. 使用 futures 和 promises(C++11及以後版本支援)

  • 設定共享資料結構(可選)
    • 雖然futures和promises本身可以處理結果傳遞,但也可以結合一個簡單的共享資料結構來更好地組織結果。例如,同樣可以定義一個std::vector來儲存任務結果(這裡假設任務結果為整數):
#include <future>
#include <vector>

std::vector<int> taskResults;
  • 在可執行物件類中設定promise
    • 在繼承自QRunnable的可執行物件類(如MyRunnable)中,需要引入std::promise來設定任務結果。示例程式碼如下:
class MyRunnable : public QRunnable {
public:
    MyRunnable(int taskId, std::promise<int>&& p) : m_taskId(taskId), m_promise(std::move(p)) {}

    void run() override {
        // 模擬執行任務,這裡簡單進行一個數學計算
        int result = m_taskId * 2;

        // 設定promise的值,即任務結果
        m_promise.set_value(result);

        qDebug() << "執行緒 " << QThread::currentThreadId() << " 完成任務 " << m_taskId;
    }

private:
    int m_taskId;
    std::promise<int> m_promise;
};
- 在上述程式碼中,建構函式接受一個`std::promise`物件,在`run`方法執行完任務後,透過`set_value`方法將任務結果設定到promise中。
  • 在主執行緒中獲取結果
    • 在主線節中,透過建立std::future物件與std::promise關聯,然後等待並獲取任務結果。示例程式碼如下:
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QThreadPool pool;
    pool.setMaxThreadCount(3);

    // 建立多個可執行物件例項並新增到執行緒池啟動執行緒執行任務
    std::vector<std::future<int>> futures;
    for (int i = 1; i <= 5; ++i) {
        std::promise<int> promise;
        MyRunnable *runnable = new MyRunnable(i, std::move(promise));
        pool.start(runnable);
        futures.push_back(promise.get_future());
    }

    // 獲取並處理結果
    for (auto& future : futures) {
        int result = future.get();
        taskResults.push_back(result);
        qDebug() << "獲取到任務結果:" << result;
    }

    qDebug() << "所有任務已完成,程式即將退出";

    return a.exec();
}
- 在上述`main`節中,首先建立`std::promise`和`std::future`物件並關聯,將`MyRunnable`物件新增到執行緒池啟動執行緒執行任務後,透過`future.get`等待並獲取每個任務的結果,然後將結果存入`taskResults`向量中並進行列印。

這些方法都可以在不使用訊號與槽機制的情況下,有效地獲取使用QThreadPool和QRunnable啟動執行緒的執行結果,你可以根據具體專案需求和程式設計環境選擇合適的方法。

相關文章