何謂並發 (What Is Concurrency)

Overview Table

概念 重點
並發的定義 兩個或兩個以上的獨立活動同時發生
任務切換 (task switching) 單核快速輪替任務,造成「並發的假象
硬體並發 (hardware concurrency) 多核/多處理器真正平行執行多個任務
Context switch 成本 保存 CPU 狀態與指令指標、載入新任務、快取重填 → 延遲
並發的兩種方式 多行程(隔離、IPC 複雜且慢)vs 多執行緒(共享位址空間、開銷小)
並發 vs 平行 平行注重效能;並發注重任務分離與回應性

何謂並發

並發 (concurrency):兩個或兩個以上的獨立活動同時發生。生活中隨處可見——一邊走路一邊說話、兩隻手同時做不同動作。

電腦系統中的並發

單核機器某一時刻只能執行一個任務,但可在單位時間內對任務多次切換——「這個任務做一會,另一個任務再做一會」,稱為任務切換 (task switching)。切換太快使人無法察覺任務曾被暫時擱置,形成「並發的假象」。

多核機器(單晶片多核處理器或多處理器)可以真正平行執行多個任務,稱為硬體並發 (hardware concurrency)

真正平行(雙核,每任務各佔一核):
Core 1: |A1|A2|A3|A4|A5|A6|A7|A8|A9|A10|
Core 2: |B1|B2|B3|B4|B5|B6|B7|B8|B9|B10|

任務切換(單核,▒ = context switch 開銷):
Core 1: |A1|▒|B1|▒|A2|▒|B2|▒|A3|▒|B3|…

Context switch(上下文切換)成本——每次任務切換,作業系統必須:

  1. 保存當前任務的 CPU 狀態與指令指標
  2. 計算要切換到哪個任務
  3. 將新任務重新載入處理器
  4. CPU 可能還要把新任務的指令與資料載入快取——期間 CPU 停止執行指令,造成額外延遲
Important

無論單核或多核、任務切換或硬體並發,C++ 標準庫提供的技術與類別都一體適用;但如何使用並發,很大程度上取決於可用的硬體並發能力

Warning

圖示為「任務均分、排程規則」的理想情況;實際上任務分割不均與排程不規則會使切換行為更混亂。任務切換與真正平行在行為上仍有微妙差異(例如時序假設),不能完全等同視之。

並發的方式:多行程 vs 多執行緒

書中比喻:開發人員 = 執行緒,辦公室 = 行程。兩人各在獨立辦公室(多行程)互不干擾但溝通困難、成本高;兩人共用一間辦公室(多執行緒)溝通容易,但可能互相干擾、爭搶資源(「參考手冊哪去了?」)。

面向 多行程並發 多執行緒並發
結構 每個行程一個執行緒 單一行程內多個執行緒
通訊方式 行程間通訊 (IPC):訊號、socket、檔案、管線 (pipe) 共享位址空間:全域變數、指標、參考與資料可直接傳遞
通訊成本 複雜且慢(作業系統保護行程,防止互改資料) ,但需自行確保各執行緒看到的資料一致
啟動/管理開銷 :啟動行程費時、OS 需資源管理行程 :執行緒如「輕量級行程」,OS 記錄工作量小
安全性 OS 提供保護 + 高階通訊機制 → 較易寫出安全的並發程式 缺乏執行緒間資料保護 → 較易出錯
分散能力 可透過遠端連線在不同機器上執行行程 僅限單一機器的單一行程內
代表 Erlang 以行程為並發基礎元件 C++ 等主流語言偏好的方式
Warning

C++ 標準未對行程間通訊提供原生支援,多行程方案必須依賴平台相關 API。因此本書(與本 vault)只關注多執行緒並發——之後所稱「並發」均指多執行緒實作。

Tip

多行程並非一無是處:行程間有 OS 保護與較高階的通訊機制,且可跨機器部署(雖增加通訊成本,但設計良好時能提升平行可用性與效能)。

並發與平行的區別

兩個概念對多執行緒而言大部分重疊,差異在關注點:


Exam/Test Patterns

情境/關鍵字 答案
「單核 CPU 也能同時跑多個程式?」 能,靠任務切換(並發的假象)
「真正同時執行多個任務」 硬體並發(多核/多處理器)
「任務切換時 OS 做什麼」 保存 CPU 狀態與指令指標 → 選擇並載入新任務 → 快取重填造成延遲
「可真正並行的任務數量由什麼決定」 硬體執行緒數量
「行程間通訊為何複雜又慢」 OS 對行程施加保護,避免行程互改資料
「執行緒間共享資料為何要小心」 共享位址空間、缺乏資料保護,必須確保資料一致性
「C++ 標準支援 IPC 嗎」 不支援,需平台 API;C++ 標準只涵蓋多執行緒
「注重效能」vs「注重任務分離/回應」 平行 vs 並發