更高階的技術可用於獲取使用QThreadPool和QRunnable啟動執行緒的執行結果

MarsCactus發表於2024-11-10

除了前面提到的方法,以下是一些更高階的技術可用於獲取使用QThreadPool和QRunnable啟動執行緒的執行結果:

1. 基於訊息佇列(Message Queue)的非同步處理

  • 設定訊息佇列
    • 可以利用第三方庫(如ZeroMQ或RabbitMQ等)來設定訊息佇列系統。以ZeroMQ為例,首先需要在專案中引入ZeroMQ庫並進行初始化。假設我們要建立一個簡單的釋出-訂閱模式的訊息佇列,用於傳遞執行緒執行結果。

    • 以下是一個簡單的初始化示例(這裡僅展示基本概念,實際應用可能需要更多配置):

#include <zmq.hpp>

zmq::context_t context(1);
zmq::socket_t socket(context, ZMQ_PUB);

// 繫結到一個本地埠,用於釋出訊息(執行緒結果)
socket.bind("tcp://*:5555");
- 這裡建立了一個ZeroMQ的上下文和一個釋出型別的套接字,並將套接字繫結到本地的一個埠,以便後續釋出執行緒執行結果。
  • 在可執行物件類中傳送訊息到佇列
    • 在繼承自QRunnable的可執行物件類(如MyRunnable)的run方法中,執行完任務後,將任務結果打包成合適的訊息格式併傳送到訊息佇列中。例如:
class MyRunnable : public QRunnable {
public:
    MyRunnable(int taskId) : m_taskId(taskId) {}

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

        // 將結果打包成訊息格式,這裡簡單使用字串表示,實際可根據需要更復雜的格式
        std::string message = std::to_string(m_taskId) + ":" + std::to_string(result);

        // 傳送訊息到訊息佇列
        zmq::message_t zmqMessage(message.size());
        memcpy(zmqMessage.data(), message.c_str(), message.size());
        socket.send(zmqMessage);

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

private:
    int m_taskId;
};
- 在上述程式碼中,執行完任務得到結果後,將任務編號和結果組合成一個字串訊息,然後建立一個ZeroMQ訊息物件並將字串內容複製進去,最後透過套接字將訊息傳送到訊息佇列中。
  • 在主執行緒中從佇列接收訊息獲取結果
    • 在主執行緒中,建立一個ZeroMQ的接收套接字並連線到釋出套接字繫結的埠,然後透過迴圈不斷接收訊息佇列中的訊息,解析出任務結果並進行處理。示例如下:
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);
    }

    // 建立接收套接字並連線到釋出套接字繫結的埠
    zmq::socket_t receiveSocket(context, ZMQ_SUB);
    receiveSocket.connect("tcp://localhost:5555");
    receiveSocket.setsockopt(ZMQ_SUBSCRIBE, "", 0);

    // 從訊息佇列接收訊息獲取結果
    while (true) {
        zmq::message_t receivedMessage;
        receiveSocket.recv(&receivedMessage);

        // 解析訊息獲取任務結果
        std::string receivedString(receivedMessage.data(), receivedMessage.size());
        size_t colonPos = receivedString.find(":");
        int taskId = std::stoi(receivedString.substr(0, colonPos));
        int result = std::stoi(receivedString.substr(colonPos + 1));

        qDebug() << "獲取到任務 " << taskId << " 的結果:" << result;

        // 可根據需要判斷是否所有任務結果都已接收,若已接收則退出迴圈
        if (taskId == 5) {
            break;
        }
    }

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

    return a.exec();
}
- 在上述`main`函式中,建立了接收套接字並進行必要的設定後,透過迴圈接收訊息佇列中的訊息,解析出任務編號和結果並進行列印,還可以根據具體情況判斷是否所有任務結果都已接收,以便決定是否退出迴圈。

2. 利用函數語言程式設計風格的非同步操作(如使用C++ 的 std::async 和 std::future)結合智慧指標

  • 設定智慧指標和任務函式
    • 首先,定義一個函式來執行具體的任務,這個函式將作為執行緒要執行的任務。例如:
#include <future>
#include <memory>

// 任務函式,模擬進行一個數學計算
int myTaskFunction(int taskId) {
    return taskId * 2;
}
- 然後,我們可以利用`std::async`來非同步啟動這個任務,並將返回的`std::future`物件用智慧指標(如`std::shared_ptr`)進行管理,以便更好地處理物件生命週期和資源管理。
  • 在主執行緒中獲取結果
    • 在主執行緒中,建立多個std::async呼叫並將它們的std::future物件用智慧指標儲存起來。然後透過迴圈遍歷這些智慧指標,呼叫get方法獲取任務結果並進行處理。示例如下:
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QThreadPool pool;
    pool.setMaxThreadCount(3);

    // 建立多個用智慧指標管理的std::async呼叫
    std::vector<std::shared_ptr<std::future<int>>> futures;
    for (int i = 1; i <= 5; ++i) {
        std::shared_ptr<std::future<int>> future = std::make_shared<std::future<int>>(std::async(std::launch::async, myTaskFunction, i));
        futures.push_back(future);
    }

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

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

    return a.exec();
}
- 在上述`main`函式中,透過`std::async`非同步啟動任務並將返回的`std/ future`物件用智慧指標儲存,然後透過迴圈遍歷這些智慧指標,呼叫`get`方法獲取任務結果並進行列印,從而實現了獲取執行緒執行結果的目的。

3. 基於Actor模型的實現

  • 定義Actor類
    • 首先建立一個類來表示Actor,在這個類中定義訊息處理函式以及相關的狀態變數等。例如:
#include <queue>

class Actor {
public:
    Actor() {}

    // 處理訊息的函式
    void handleMessage(const std::string& message) {
        // 解析訊息,假設訊息格式為 "taskId:result"
        size_t colonPos = message.find(":");
        int taskId = std::stoi(message.substr(0, colonPos));
        int result = std::stii(message.substr(colonPos + 1));

        // 將結果儲存到內部狀態變數
        m_results.push_back({taskId, result});

        qDebug() << "Actor收到任務 " << taskId << " 的結果:" << result;
    }

    // 獲取所有結果的函式
    std::vector<std::pair<int, int>> getResults() {
        return m_results;
    }

private:
    std::queue<std::pair<int, int>> m_results;
};
- 在上述程式碼中,`Actor`類有一個`handleMessage`函式用於處理接收到的訊息(假設訊息格式為`taskId:result`),解析出任務結果並儲存到內部的佇列中。還有一個`getResults`函式用於獲取所有儲存的結果。
  • 在可執行物件類中傳送訊息給Actor
    • 在繼承自QRunnable的可執行物件類(如MyRunnable)的run方法中,執行完任務後,將任務結果按照與Actor約定的訊息格式傳送給Actor。例如:
class MyRunnable : public QRunnable {
public:
    MyRunnable(int taskId, Actor* actor) : m_taskId(taskId), m_actor(actor) {}

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

        // 將結果打包成訊息格式併傳送給Actor
        std::string message = std::to_string(m_taskId) + ":" + std::to_string(result);
        m_actor->handleMessage(message);

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

private:
    int m_taskId;
    Actor* m_actor;
};
- 在上述程式碼中,執行完任務得到結果後,將任務編號和結果組合成一個字串訊息,然後傳送給指定的Actor,由Actor進行處理和儲存結果。
  • 在主執行緒中獲取結果
    • 在主執行緒中,建立Actor物件,並將其作為引數傳遞給每個MyRunnable物件,然後透過呼叫Actor的getResults函式獲取所有任務結果並進行處理。示例如下:
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QThreadPool pool;
    pool.setMaxThreadCount(3);

    // 建立Actor物件
    Actor actor;

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

    // 獲取並處理結果
    std::vector<std::pair<int, int>> results = actor.getResults();
    for (const auto& result : results) {
        qDebug() << "獲取到任務 " << result.first << " 的結果:" << result.second;
    }

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

    return a.exec();
}
- 在上述`main`函式中,建立了Actor物件並將其作為引數傳遞給每個`Runnable`物件,在所有任務完成後,透過呼叫Actor的`getResults`函式獲取所有任務結果並進行列印,從而實現了獲取執行緒執行結果的目的。

這些高階技術在不同的場景下各有優劣,可以根據具體的專案需求、效能要求和開發團隊的熟悉程度等因素來選擇合適的方法來獲取執行緒執行結果。

相關文章