執行緒數量與識別 (Thread Count and Identification)

Overview Table

概念 重點
hardware_concurrency() 回傳可真正並行的執行緒數(如 CPU 核心數);僅為提示,無法取得時回傳 0
執行緒數量公式 num_threads = min(硬體執行緒數(0 則取預設值), max_threads);避免過多執行緒造成上下文切換
parallel_accumulate 分塊 → 啟動 num_threads-1 條(主執行緒算最後一塊)→ 全部 join → 彙總
使用限制 浮點加法不滿足結合律 → 結果可能異於 std::accumulate;需前向迭代器、T 有預設建構函式
std::thread::id get_id() / std::this_thread::get_id();可拷貝、比較、排序、雜湊,適合當容器鍵

確定執行緒數量:hardware_concurrency()

std::thread::hardware_concurrency() 回傳系統可並行的執行緒數量——多核心系統上通常是 CPU 核心數

不要直接拿回傳值當執行緒數:一要處理回傳 0 的情況,二要與「工作量能支撐的最大執行緒數」取小,免得資料太少卻開一堆執行緒。

平行版 accumulate(代碼 2.9)

把整體工作拆成小塊分給各執行緒,並設定最小任務量避免執行緒過多:

length = distance(first, last)          ── 空範圍?→ 直接回傳 init
max_threads = (length + min_per_thread - 1) / min_per_thread   // 天花板除法
hardware_threads = std::thread::hardware_concurrency()
num_threads = min(hardware_threads != 0 ? hardware_threads : 2, max_threads)
block_size = length / num_threads
std::vector<T> results(num_threads);
std::vector<std::thread> threads(num_threads - 1);   // 主執行緒也算一條!
Iterator block_start = first;
for (unsigned long i = 0; i < (num_threads - 1); ++i) {
    Iterator block_end = block_start;
    std::advance(block_end, block_size);
    threads[i] = std::thread(accumulate_block<Iterator,T>(),
                             block_start, block_end, std::ref(results[i]));
    block_start = block_end;
}
accumulate_block<Iterator,T>()(block_start, last, results[num_threads-1]); // 主執行緒處理最終塊
for (auto& entry : threads) entry.join();
return std::accumulate(results.begin(), results.end(), init);

流程總覽:

輸入範圍 ──分塊──▶ 塊1 塊2 ... 塊N-1        最終塊
                    │    │        │            │
                 執行緒1 執行緒2 執行緒N-1   主執行緒(⑨)
                    │    │        │            │
                    └────┴── join 全部 ────────┘
                              │
                    accumulate(results) → 總結果

注意事項(使用限制)

執行緒識別:std::thread::id

執行緒識別碼型別為 std::thread::id,取得方式有兩種:

方式 寫法 說明
從物件取得 t.get_id() 若物件未關聯執行緒,回傳預設建構值,代表「無執行緒」
從當前執行緒取得 std::this_thread::get_id() 定義於 <thread>,取得自己的 id

性質:

典型用途

  1. 主執行緒判斷:啟動其他執行緒前記下自己的 id,演算法中比對以決定是否執行主執行緒專屬工作
std::thread::id master_thread;
void some_core_part_of_algorithm() {
    if get_id() == master_thread
        do_master_thread_work();
    do_common_work();
}
  1. 存進資料結構比對當前執行緒,決定操作是「允許」還是「需要」(permitted/required)
  2. 當容器鍵值:替執行緒關聯專屬資訊(執行緒本地儲存不適用時的替代方案)、或於執行緒間互傳資訊

Exam/Test Patterns

情境/關鍵字 答案
hardware_concurrency() 回傳 0 代表無法取得資訊;自選預設值(書中用 2)
「該開幾條執行緒?」 min(硬體執行緒數, (length+min_per_thread-1)/min_per_thread)——工作量與硬體取小
parallel_accumulate 為何只啟動 num_threads-1 主執行緒也是一條,負責處理最終塊
float 平行加總結果與序列版不同 浮點加法不滿足結合律,分塊改變運算順序
「執行緒怎麼把結果傳回來?」 不能直接回傳:傳 std::ref 參考進去,或用 future(第 4 章)
判斷目前是否為 master thread 比對 std::this_thread::get_id() 與預存的 std::thread::id
std::thread::id 能否當 map/unordered_map 的鍵 可以:支援全序比較與 std::hash<std::thread::id>
未關聯執行緒的 t.get_id() 回傳預設建構的 id,表示「無執行緒」