函數式與訊息傳遞風格 (Functional and Message-Passing Styles)

Overview Table

概念 重點
FP 風格併發 純函式不改共享狀態 → 無競爭條件、免互斥鎖;future 讓結果可互相依賴
FP 快速排序(串行) splice 取 pivot + std::partition 分割 + 遞迴(代碼 4.12)
FP 快速排序(平行) lower 半部以 std::async 交給另一執行緒;new_lower.get() 取回
spawn_task packaged_task + std::thread + detach 的手工替代(代碼 4.14)
CSP / Actor 模型 無共享資料;執行緒 = 狀態機;透過訊息佇列通訊
ATM 範例 三執行緒(硬體/邏輯/銀行);wait().handle<Msg>() 鏈;不匹配訊息被丟棄

函數式程式設計與併發

函數式程式設計 (FP):函式結果只依賴傳入的參數,相同參數呼叫多少次都得到相同結果(如 sincossqrt);純函式不改變任何外部狀態。

FP 快速排序:串行版(代碼 4.12)

介面是 FP 的:傳入 list、回傳新的已排序 list(不像 std::sort() 原地修改);內部以 splice() 移動節點避免大量拷貝。步驟:

  1. result.splice(result.begin(), input, input.begin()) — 取第一個元素為 pivot 移入結果
  2. T const& pivot = *result.begin() — 以引用避免拷貝
  3. std::partition(input.begin(), input.end(), [&](T const& t){ return t < pivot; }) — 分成「小於 pivot」與「不小於 pivot」兩段,回傳分割點迭代器
  4. lower_part.splice(...) — 小於 pivot 的節點移入新 list
  5. lower_part 與剩餘的 input 分別遞迴(用 std::move 傳入)
  6. result.splice() 兩次:new_higher 接在 pivot 之後、new_lower 接在 pivot 之前

FP 快速排序:平行版(代碼 4.13)

純函式模式容易轉平行——兩半的排序互不相干,只需把其中一半交給另一個執行緒:

std::future<std::list<T>> new_lower(                        // 差異 1:async
    std::async(&parallel_quick_sort<T>, std::move(lower_part)));
auto new_highermove(input));     // 本執行緒遞迴
result.splice(result.end(), new_higher);
result.splice(result.begin(), new_lower.get());             // 差異 2:get() 等待並取回
input ──splice──▶ pivot(移入 result)
  │
  ├─ std::partition(t < pivot)
  │
  ├─ lower_part ──[std::async 另一執行緒]──▶ new_lower(future)
  └─ input(≥ pivot)──[本執行緒遞迴]───────▶ new_higher
                                                │
result = new_lower.get() ++ [pivot] ++ new_higher ◀─ splice 拼接
平行版仍非最佳

std::partition 仍是串行呼叫,是效能天花板。追求最快平行排序可查閱學術文獻,或用 C++17 的平行多載版本(見 10-Parallel-Algorithms/01-Execution-Policies)。

spawn_task:packaged_task + thread 的手工包裝(代碼 4.14)

template<typename F, typename A>
std::future<std::result_of_t<F(A&&)>> spawn_task(F&& f, A&& a) {
    using result_type = std::result_of_t<F(A&&)>;
    std::packaged_task<result_type(A&&)> taskmove(f);
    std::future<result_type> res(task.get_future());
    std::thread tmove(task), std::move(a);
    t.detach();               // 任務完成時 future 自動就緒
    return res;
}

CSP 與 Actor 模型(4.4.2)

CSP(Communicating Sequential Processes,通訊順序進程):

Actor 模型(參與者模式):系統中有多個獨立的參與者 (actor),各自運行在獨立執行緒上,互相發送訊息執行任務,除了透過訊息傳入的內容外不共享任何狀態

ATM 狀態機範例(代碼 4.15、4.16)

為 ATM(自動提款機)分三個執行緒:處理實體機械ATM 邏輯與銀行通訊——互不共享資料,只互發訊息(如插卡時,機械執行緒發訊息給邏輯執行緒;邏輯執行緒發「吐鈔金額」訊息給機械執行緒)。

ATM 邏輯建模為狀態機(圖 4.3 簡化):

          卡片插入              PIN 輸入完成(滿4位)
┌──────────────────┐    ┌─────────────┐    ┌───────────────┐
│ waiting_for_card │──▶│ getting_pin │──▶│ verifying_pin │
└──────▲───────────┘    └──┬───▲──┬───┘    └──────┬────────┘
       │           digit/清除│   │  │取消          │
       │            (自身迴圈)└───┘  ▼           驗證失敗──▶ 退卡 ─┐
       │                     done_processing ◀── 取消          │
       │                            ▲          驗證通過        │
       └────── 退卡、等下一位 ◀─────┴── 取款:等銀行確認 ──────┘
                                        (吐鈔 / 餘額不足)

實作:每個狀態一個成員函式,狀態轉移 = 改變成員函式指標;主迴圈反覆呼叫當前狀態函式:

void atm::waiting_for_card() {
    interface_hardware.send(display_enter_card());
    incoming.wait()                              // 等待傳入訊息
        .handle<card_inserted>([&](card_inserted const& msg) {
            account = msg.account; pin = "";
            interface_hardware.send(display_enter_pin());
            state = &atm::getting_pin;           // 狀態轉移
        });
}
// run(): state = &atm::waiting_for_card; for(;;) (this->*state)();
分離關注點

這種設計無需考慮同步與併發問題,只需考慮「什麼時候收、發哪些訊息」;所有同步都封裝在訊息傳遞庫內。每個執行緒被完全獨立對待,極大簡化併發系統的任務設計。完整的訊息傳遞框架實作見 12-Appendix/03-ATM-Message-Passing-Example


Exam/Test Patterns

情境/關鍵字 答案
「純函式與併發的關係」 不修改共享狀態 → 無競爭條件、無需互斥鎖
「FP 平行快排哪一半在新執行緒」 lower 半部用 std::async;higher 半部本執行緒遞迴;以 new_lower.get() 取回
「遞迴呼叫 async 的執行緒數」 每層倍增:10 層 ≈ 1024 執行緒;預設策略下運行庫可同步執行超額任務
「spawn_task 的組成」 packaged_task + get_future() + std::thread + detach()
「CSP 的核心思想」 無共享資料;執行緒 = 狀態機;經訊息佇列通訊
「Actor 模型」 獨立參與者(各在一執行緒)互發訊息,除訊息外不共享狀態
「handle 收到不匹配的訊息型別」 丟棄,繼續等待匹配型別的訊息
「平行快排仍存在的瓶頸」 std::partition 是串行;改用 C++17 平行演算法(第10章)