除了訊息佇列,以下這些高階技術也可用於獲取執行緒執行結果

MarsCactus發表於2024-11-10

除了訊息佇列,以下這些高階技術也可用於獲取執行緒執行結果:

1. 基於共享記憶體(Shared Memory)與記憶體對映檔案(Memory-Mapped Files)

  • 共享記憶體機制
    • 共享記憶體允許不同程序(包括由執行緒池啟動的不同執行緒所在的程序)直接訪問同一塊記憶體區域,從而實現高效的資料共享。在獲取執行緒執行結果方面,可以定義一個共享記憶體區域來儲存執行緒執行的結果。
    • 例如,在Linux系統下,可以使用shmgetshmat等系統呼叫實現共享記憶體的建立、掛載等操作。以下是一個簡單示例的虛擬碼思路:
#include <sys/types.h>
#include <sys/shm.h>
#include <stdio.h>

// 定義共享記憶體結構體來儲存結果
struct SharedResult {
    int taskId;
    int result;
};

int main(int argc, char *argv[]) {
    // 建立共享記憶體段
    int shmid = shmget(IPC_PRIVATE, sizeof(SharedResult), IPC_CREAT | 0666);
    if (shmid < 0) {
        perror("shmget");
        return -1;
    }

    // 將共享記憶體段掛載到程序地址空間
    struct SharedResult *sharedResult = (struct SharedResult *)shmat(shmid, NULL, 0);
    if (sharedResult == (void *)-1) {
        perror("shmat");
        return -1;
    }

    QThreadPool pool;
    pool.setMaxThreadCount(3);

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

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

    // 從共享記憶體讀取結果
    for (int i = 1; i <= 5; ++i) {
        printf("獲取到任務 %d 的結果:%d\n", sharedResult->taskId, sharedResult->result);
    }

    // 分離共享記憶體段
    if (shmdt(sharedResult) < 0) {
        perror("shmdt");
        return -1;
    }

    // 刪除共享記憶體段
    if (shmctl(shmid, IPC_RMID, NULL) < 0) {
        perror("shmctl");
        return -1;
    }

    return 0;
}
- 在上述示例中,首先建立了共享記憶體段並掛載到程序地址空間,然後將共享記憶體區域的指標傳遞給每個可執行物件例項(假設`MyRunnable`類建構函式接受這個指標),執行緒在執行任務完成後將結果寫入共享記憶體區域。最後,在主執行緒中從共享記憶體讀取結果,並進行相應的清理操作(分離和刪除共享記憶體段)。
  • 記憶體對映檔案
    • 記憶體對映檔案是一種將檔案內容對映到程序記憶體空間的技術,使得對檔案的操作如同對記憶體的操作一樣便捷。它也可以用於獲取執行緒執行結果,透過將執行緒執行結果寫入到一個特定的檔案中,然後在主執行緒中透過記憶體對映的方式讀取該檔案來獲取結果。
    • 例如,在Linux系統下,可以使用mmap系統呼叫實現記憶體對映檔案操作。以下是一個簡單示例的虛擬碼思路:
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>

// 定義結構體來儲存結果(假設結果寫入檔案)
struct FileResult {
    int taskId;
    int result;
};

int main(int argc, char *argv[]) {
    // 建立一個臨時檔案用於儲存結果
    int fd = open("temp_result_file", O_CREAT | O_RDWR, 0666);
    if (fd < 0) {
        perror("open");
        return -1;
    }

    // 將檔案對映到記憶體空間
    struct FileResult *fileResult = (struct FileResult *)mmap(NULL, sizeof(FileResult) * 5, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (fileResult == (void *)-1) {
        perror("mmap");
    =-1;
    }

    QThreadPool pool;
    pool.setMaxThreadCount(3);

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

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

    // 從記憶體對映檔案讀取結果
    for (int i = 1; i <= 5; ++i) {
        printf("獲取到任務 %d 的結果:%d\n", fileResult->taskId, fileResult->result);
    }

    // 解除記憶體對映
    if (munmap(fileResult, sizeof(FileResult) * 5) < 0) {
        perror("munmap");
        return -1;
    }

    // 關閉檔案
    if (close(fd) < 0) {
        perror("close");
        return -1;
    }

    // 刪除臨時檔案(可選)
    remove("temp_result_file");

    return 0;
}
- 在上述示例中,首先建立了一個臨時檔案並將其對映到記憶體空間,然後將記憶體對映區域的指標傳遞給每個可執行物件例項(假設`MyRunnable`類建構函式接受這個指標),執行緒在執行任務完成後將結果寫入記憶體對映檔案區域。最後,在主執行緒中從記憶體對映檔案讀取結果,並進行相應的清理操作(解除記憶體對映、關閉檔案和刪除臨時檔案)。

2. 基於事件驅動架構(Event-Driven Architecture)

  • 原理及基本構成

    • 事件驅動架構以事件為核心,當某個事件發生時,相關的處理邏輯被觸發。在獲取執行緒執行結果的場景下,可以將執行緒執行完成作為一個事件,透過註冊相應的事件處理函式來獲取並處理結果。
    • 通常由事件源(如執行緒執行完成)、事件佇列(用於暫存發生的事件)、事件處理器(處理事件的邏輯)等組成。
  • 示例實現

    • 首先定義事件結構體和事件處理器函式型別:
#include <functional>

// 定義事件結構體,這裡表示執行緒執行完成事件
struct ThreadFinishEvent {
    int taskId;
    int result;
};

// 定義事件處理器函式型別
using EventHandler = std::function<void(const ThreadFinishEvent&)>;
- 然後,在可執行物件類(如`MyRunnable`)的`run`方法中,執行完任務後觸發事件:
class MyRunnable : public QRunnable {
public:
    MyRunnable(int taskId, EventHandler handler) : m_taskId(taskId), m_handler(handler) {}

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

        // 觸發執行緒執行完成事件
        ThreadFinishEvent event = {m_taskId, result};
        m_handler(event);

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

private:
    int m_taskId;
    EventHandler m_handler;
};
- 最後,在主執行緒中註冊事件處理函式並啟動執行緒執行任務:
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QThreadPool pool;
    pool.setMaxThreadCount(3);

    // 註冊事件處理函式
    EventHandler handler = [](const ThreadFinishEvent& event) {
        printf("獲取到任務 %d 的結果:%d\n", event.taskId, event.result);
    };

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

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

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

    return a.exec();
}
- 在上述示例中,透過定義事件結構體和事件處理器函式型別,在可執行物件類中觸發事件,以及在主執行緒中註冊事件處理函式,實現了以事件驅動的方式獲取執行緒執行結果。

3. 基於分散式系統相關技術(如RPC、分散式鎖等)

  • 遠端過程呼叫(RPC)
    • RPC允許一臺計算機上的程式呼叫另一臺計算機上的程式中的函式,就好像呼叫本地函式一樣。在獲取執行緒執行結果方面,如果執行緒執行任務分佈在不同的計算機上(例如在分散式計算環境下),可以利用RPC來獲取結果。
    • 例如,使用gRPC(Google Remote Procedure Call)等框架,首先需要定義服務介面和訊息格式。假設我們定義一個簡單的服務介面用於獲取執行緒執行結果:
import "google/protobuf/any.proto";

service ResultService {
    rpc GetResult(google/protobuf/any.proto.Any) returns (google/protobuf/any.proto.Any);
}
- 然後,在可執行物件類(如`MyRunnable`)執行完任務後,透過RPC呼叫將結果傳送到指定的服務端,由服務端進行儲存和管理。在主執行緒中,透過再次呼叫RPC從服務端獲取結果。具體實現涉及到更多的框架相關配置和程式碼編寫,這裡僅展示基本思路。
  • 分散式鎖
    • 分散式鎖用於在分散式系統中協調多個程序或執行緒對共享資源的訪問。在獲取執行緒執行結果的場景下,可以利用分散式鎖來確保只有一個執行緒或程序能夠獲取到最新的結果,避免結果的混亂。
    • 例如,使用etcd(一個分散式鍵值儲存系統)或Zookeeper等工具來實現分散式鎖。假設我們使用etcd,首先需要安裝和配置etcd,然後在可執行物件類中,執行完任務後嘗試獲取分散式鎖,將結果寫入到一個共享的鍵值儲存中(透過etcd的API),然後釋放分散式鎖。在主執行緒中,透過查詢etcd的鍵值儲存來獲取結果。同樣,具體實現涉及到更多的工具相關配置和程式碼編寫,這裡僅展示基本思路。

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

相關文章