持續性、Latch 與 Barrier (Continuations, Latches and Barriers)

Overview Table

概念 重點
std::experimental::future 並發技術規範 (Concurrency TS) 擴充,支援持續性 (continuation)
.then(continuation) 「完事俱備,只等資料」;持續性函式的參數是就緒的 future 本身
持續性連接鏈 .then().then()...;例外沿鏈傳播;future 展開 (future-unwrapping)
when_all 全部就緒 → 新 future 就緒;搭配 .then 免輪詢、免佔用執行緒
when_any 任一就緒即觸發;when_any_result{ futures, index }
latch(鎖存器) 一次性計數器:count_down 至 0 → 就緒並保持就緒
barrier(柵欄) 可重複使用的組同步:arrive_and_wait / arrive_and_drop
flex_barrier 完成階段函式;回傳值可改下一週期執行緒數(−1 = 不變)

並發技術規範與 experimental::future

標準化現況

C++20 已將 latch/barrier 標準化為 std::latchstd::barrier(後者含完成函式,概念近似 flex_barrier);future 的 .then() 持續性則尚未進入標準。

then():為 future 添加持續性

std::future 處理「結果就緒後的下一步」必須以 wait()/get()/wait_for() 阻塞等待,程式碼複雜。持續性的意義是「完事俱備,只等資料」——future 就緒時自動執行後續處理:

std::experimental::future<int> fut = find_the_answer();
auto fut2 = fut.then(find_the_question);  // 就緒時自動調用
assert(!fut.valid());                     // 原 future 失效
assert(fut2.valid());                     // 新 future 持有持續性的結果

spawn_async(代碼 4.17,與 std::async 等價的手工版):

std::thread t([p = std::move(p), f = std::decay_t<Func>(func)]() mutable {
    try { p.set_value_at_thread_exit(f()); }
    catch (...) { p.set_exception_at_thread_exitcurrent_exception(); }
});
t.detach();
return res;   // res = p.get_future()

持續性連接鏈(4.4.4)

使用者登入流程(驗證 → 取帳戶資訊 → 更新畫面)的四個版本:

版本 作法 問題
同步(4.18) 依序呼叫 + try/catch 阻塞呼叫端執行緒(UI 卡住)
async(4.19) 整段包進 std::async 仍佔用一條執行緒等待後端,任務多時耗資源
持續性(4.20) spawn_async(...).then(...).then(...) 每段任務中對後端的呼叫仍會阻塞該任務執行緒
全異步(4.21) 後端 API 也回傳 future + .then + 展開 鏈上完全不阻塞
return backend.async_authenticate_user(username, password)
    .thenfuture<user_id> id {
        return backend.async_request_current_info(id.get());   // 回傳 future → 自動展開
    })
    .thenfuture<user_data> info {
        try { update_display(info.get()); }
        catch exception& e) { display_error(e; }        // 全鏈例外統一處理
    });

std::experimental::shared_future 也支援持續性,差異:

when_all:等待全部就緒(4.4.5)

std::async 收集多個 future(代碼 4.22)的缺點:收集任務佔用一條執行緒,對每個未就緒的 f.get() 反覆「喚醒 → 發現未就緒 → 再休眠」,增加上下文切換與額外開銷。

std::experimental::when_all(first, last) 回傳新 future:全部傳入的 future 就緒時才就緒,搭配 .then 無阻塞收集:

return std::experimental::when_all(results.begin(), results.end())
    .then([](auto ready_results) {   // future<vector<experimental::future<ChunkResult>>>
        std::vector<ChunkResult> v;
        for (auto& f : ready_results.get())
            v.push_back(f.get());    // 不會阻塞:此刻全部已就緒
        return gather_results(v);
    });

when_any:等待第一個就緒(4.4.6)

情境:多個任務平行搜尋資料子集,找到任一符合值即可;第一個完成者觸發後續處理,其他任務透過共享的 std::atomic<bool> done_flag 提早停止。

latch:一次性計數同步(4.4.8)

latch(鎖存器)是輕量級同步物件:計數器減到 0 時進入就緒態,之後保持就緒直到銷毀——適合同步「一組一次性事件」。

成員函式latch,<experimental/latch> 作用
建構函式(唯一參數 = 計數值) 設定要等待的事件數
count_down() 計數器減 1
wait() 阻塞直到就緒(計數 == 0)
is_ready() 查詢是否就緒
count_down_and_wait() 減 1 並等待歸 0
std::experimental::latch done(thread_count);
for (unsigned i = 0; i < thread_count; ++i)
    threads.push_back(std::async(std::launch::async, [&, i] {
        data[i] = make_data(i);
        done.count_down();   // 資料就緒即遞減,不必等整個任務結束
        do_more_stuff();
    }));
done.wait();                 // 只等「資料就緒」,不等任務全部完成
process_data(data, thread_count);

barrier:可重複的組同步(4.4.9)

barrier(柵欄)用於一組執行緒間的可重複同步:<experimental/barrier> 提供 std::experimental::barrier(簡單、低開銷)與 std::experimental::flex_barrier(靈活、開銷較大)。

代碼 4.26 的流水線模式(來源 source → 平行處理 → 輸出 sink):

每個週期(處理一個 data_block):
  thread 0                 thread 1..N-1
  讀取 + 切塊(串行)          (等待)
     │                          │
     ├── sync.arrive_and_wait() ┤   ◀ 同步點 1:確保資料切好才開工
     │                          │
  process(chunks[0])       process(chunks[i])   ◀ 平行處理各自區塊
     │                          │
     ├── sync.arrive_and_wait() ┤   ◀ 同步點 2:確保全部寫入 result
     │                          │
  sink.write_data(result)   (等待 / 下一週期)   ◀ 串行部分只在 0 號執行緒
arrive_and_wait 的擺放位置

同步點 1:所有執行緒等 0 號執行緒切好資料才開始平行處理;同步點 2 相反:0 號執行緒等其他人都寫完才輸出結果。串行段執行時,必須確定沒有其他執行緒還在跑。

flex_barrier:帶完成階段(4.4.10)

std::experimental::flex_barrier 多一個建構參數:完成函式——所有執行緒到達柵欄後,由其中一個執行緒執行它(天然的串行區)。

std::experimental::flex_barrier sync(num_threads, [&] {
    sink.write_datamove(result);
    split_source();   // 準備下一輪資料(在 0 號執行緒等單一執行緒上跑)
    return -1;        // 下一週期執行緒數不變
});
// 工作執行緒:while (!source.done()) { result.set_chunk(..., process(chunks[i])); sync.arrive_and_wait(); }

Exam/Test Patterns

情境/關鍵字 答案 「future 就緒後自動接續處理」 .then(continuation);持續性參數是就緒的 future 本身 「呼叫 .then 之後原 future」 失效(!valid());.then 回傳新 future 「持續性函式回傳 future」 future 展開:.then() 結果是 future<T>,非巢狀 「持續性鏈中發生例外」 沿鏈傳播,於下游 .get() 時拋出,末端 catch 統一處理 「等全部 / 等任一 future」 when_all / when_any(when_any_result{futures, index}) 「輪詢多個 future 浪費資源」 when_all + .then:免輪詢、免佔用執行緒 「一次性事件的計數同步」 latch:count_down / wait;就緒後保持,不重置 「一組執行緒多階段重複同步」 barrier:arrive_and_wait;退出組用 arrive_and_drop 「同步點後要跑一段串行碼/改變組大小」 flex_barrier 完成函式;回傳 −1 維持人數 「shared_future 的持續性」 可掛多個;持續性參數必須是 shared_future 「future 就緒前確保 thread_local 清理」 set_value_at_thread_exit / set_exception_at_thread_exit