Item 36:若非同步執行不可或缺,就指定 std::launch::async (Specify std::launch::async if Asynchronicity Is Essential)

Overview Table

重點 說明
兩種 launch policy std::launch::async(必須在另一條 thread 非同步執行)與 std::launch::deferred(延遲到對 future 呼叫 get/wait同步執行
預設政策 兩者 OR 起來std::launch::async | std::launch::deferred,即由執行期系統自行決定非同步或延遲
預設政策的代價 無法預測是否併發執行、在哪條 thread 執行、甚至是否執行thread_local 變數歸屬不確定;wait_for/wait_until 迴圈可能永不終止
修正手法 先以 fut.wait_for(0s) 檢查是否回傳 std::future_status::deferred,再決定走同步路徑或 timeout 迴圈
核心結論 若任務必須真正非同步執行,呼叫時明確傳入 std::launch::async(或使用自製的 reallyAsync

兩種 Launch Policy 與「出乎意料」的預設值

std::async 並不是「請幫我非同步執行 f」,而是「請依 launch policy 執行 f」。policy 是 std::launch 這個 scoped enum(見 Item 10)的列舉值:

Policy 語意 執行時機 / 執行緒
std::launch::async f 必須非同步執行 立即排程於不同 thread
std::launch::deferred f 延遲執行 僅當對 future 呼叫 getwait 時,於呼叫者 thread 同步執行(呼叫者阻塞至 f 結束);若從未呼叫 get/waitf 永遠不會執行
auto fut1 = std::async(f);   // 使用「預設」launch policy

auto fut2 = std::async(std::launch::async |   // 與 fut1 完全等價:
                       std::launch::deferred, // async「或」deferred,
                       f);                    // 由系統挑選
Important

預設政策 = async | deferred。這個彈性讓標準函式庫得以接手 thread 建立/銷毀、避免 oversubscription、負載平衡(Item 35 的優點來源),但也帶來下述不確定性。

預設政策的三大不確定性

設 thread t 執行 auto fut = std::async(f);

  1. 無法預測 f 是否與 t 併發執行——f 可能被排為 deferred。
  2. 無法預測 f 是否跑在與呼叫 get/wait 的 thread 不同的 thread 上
  3. 可能無法預測 f 是否會執行——若某些程式路徑不會對 fut 呼叫 get/wait,deferred 的 f 就永遠不執行。

連帶影響:

using namespace std::literals;      // C++14 duration 字尾

void f() { std::this_thread::sleep_for(1s); }  // f 睡 1 秒後返回

auto fut = std::async(f);           // 「概念上」非同步執行 f

while (fut.wait_for(100ms) !=       // 等 f 結束……
       std::future_status::ready)   // 若 f 被 deferred,
{ /* ... */ }                       // 迴圈永不終止!
預設政策下 wait_for 的兩種世界:
              ┌── policy 實為 async ────► wait_for 最終回傳 ready ──► 迴圈結束
std::async(f)─┤
              └── policy 實為 deferred ─► wait_for 永遠回傳 deferred ► 無窮迴圈
Danger

這種 bug 在開發與單元測試時極易漏掉:只有在高負載(機器接近 oversubscription 或 thread exhaustion)時,任務才最可能被 deferred,問題才浮現。

修正:以 wait_for(0s) 偵測 deferred

future 沒有「是否 deferred」的直接查詢 API,只能借用 timeout 函式、傳入 0 逾時來探測:

auto fut = std::async(f);                    // 同前

if (fut.wait_for(0s) ==                      // 任務被 deferred?
    std::future_status::deferred)
{
  /* 對 fut 用 wait 或 get 同步呼叫 f */
} else {                                     // 任務不是 deferred
  while (fut.wait_for(100ms) !=              // 不可能無窮迴圈
         std::future_status::ready)          // (假設 f 會結束)
  {
    /* 任務尚未 ready,先做其他併發工作 */
  }
  /* fut 已 ready */
}

預設政策的適用檢查表——以下全部成立時,用預設政策才安全:

# 條件
1 任務不需要與呼叫 get/wait 的 thread 併發執行
2 讀寫哪條 threadthread_local 變數無關緊要
3 保證會對 future 呼叫 get/wait可接受任務永不執行
4 使用 wait_for/wait_until 的程式碼已考慮 deferred 狀態

任一條不成立 → 明確指定 std::launch::async

auto fut = std::asyncasync, f;   // 保證真正非同步執行 f
Warning

「對 std::async 回傳的 future 呼叫 get/wait」是簡化說法:真正關鍵的是 future 所指涉的 shared state(見 Item 38)。future 可被 move、也可轉成可複製的 std::shared_future,因此實際觸發執行的 future 物件未必是 std::async 直接回傳的那一個。

自製 reallyAsync:永遠使用 async 政策

// C++11:以 std::result_of 推出回傳型別(type traits 見 Item 9)
template<typename F, typename... Ts>
inline
std::future<typename std::result_of<F(Ts...)>::type>
reallyAsync(F&& f, Ts&&... params)          // 回傳非同步呼叫
{                                           // f(params...) 的 future
  return std::async(std::launch::async,
                    std::forward<F>(f),     // 完美轉發(見 Item 25)
                    std::forward<Ts>(params)...);
}

// C++14:auto 回傳型別推導,更精簡
template<typename F, typename... Ts>
inline
auto reallyAsync(F&& f, Ts&&... params)
{
  return std::async(std::launch::async,
                    std::forward<F>(f),
                    std::forward<Ts>(params)...);
}

auto fut = reallyAsync(f);   // 非同步執行 f;若 std::async
                             // 無法建立 thread 則丟出例外

Exam/Test Patterns

情境關鍵字 答案
std::async(f) 的預設 launch policy 是什麼? std::launch::async | std::launch::deferred(兩者 OR,由系統決定)
std::launch::deferred 的任務何時執行? 對 future 呼叫 get/wait 時,在呼叫者 thread 同步執行;從未呼叫則永不執行
while (fut.wait_for(100ms) != ready) 永不終止 任務被 deferredwait_for 永遠回傳 std::future_status::deferred
如何檢查任務是否被 deferred? 呼叫 fut.wait_for(0s) 並比對 std::future_status::deferred(無直接查詢 API)
bug 只在高負載/正式環境出現、測試抓不到 oversubscription / thread exhaustion 時任務才容易被 deferred
f 使用 thread_local 變數 + 預設政策 無法預測存取哪條 thread 的 TLS → 應指定 std::launch::async
「任務必須真正併發執行」 傳入 std::launch::async 作第一個引數,或用 reallyAsync 包裝
reallyAsync 的實作要點 完美轉發 f 與參數給 std::async,固定 std::launch::async;C++11 用 std::result_of 寫回傳型別、C++14 用 auto