CPU 排程與情境切換完全指南

理解作業系統如何把執行緒排上 CPU:排程器、時間片、搶佔式 vs 協作式、排程演算法與情境切換成本


目錄


為什麼需要 CPU 排程?

一台機器的 CPU 核心數有限,但同時要執行的執行緒往往遠多於核心數。CPU 排程(CPU Scheduling) 就是作業系統決定「哪條執行緒、在哪個核心、執行多久」的機制。

可執行的執行緒(就緒佇列): T1  T2  T3  T4  T5 …
CPU 核心:                  Core0  Core1
                              ↑      ↑
               排程器決定:誰上、上多久、何時換下

目標:在有限核心上,讓眾多執行緒看起來像「同時」在跑,同時兼顧公平、回應速度與吞吐量。


執行緒為何是排程單位

現代作業系統中,CPU 排程的基本單位是執行緒(kernel thread),而非行程:

  • 行程是資源擁有單位(位址空間、檔案…)
  • 執行緒是執行 / 排程單位(有自己的 stack、PC、暫存器)

排程器從「就緒(Ready)」的執行緒中挑一條放上 CPU。

Linux 的特例:Linux 排程器排的是 "task"(task_struct),它不嚴格區分行程與執行緒——同一行程的多條執行緒只是「共享資源的 task」。概念上仍是「以執行流為單位排程」。

使用者層執行緒不在此列:協程 / goroutine / 纖程是在使用者空間被排到核心執行緒上,OS 不直接排它們(見 執行緒模型筆記)。


並發 vs 並行

兩個最常被搞混的詞:

概念 定義 關鍵
並發(Concurrency) 多個任務在同一段時間內推進,可能靠快速切換交錯執行 結構:能同時「處理」多件事
並行(Parallelism) 多個任務在同一時刻真的同時執行 需要多核心,真的同時跑
並發(單核):  T1▮ T2▮ T1▮ T2▮ T1▮   ← 快速切換,看起來同時,實際輪流
並行(雙核):  Core0: T1▮▮▮▮▮
               Core1: T2▮▮▮▮▮         ← 真的同一時刻一起跑
  • 單核心也能並發(靠排程切換),但無法並行
  • 並行一定是並發的一種,但並發不一定並行

名言(Rob Pike):並發是「同時處理很多事」的能力,並行是「同時執行很多事」。 並發是結構設計,並行是執行表現。


硬體核心與執行緒數量

真正並行的執行緒數量,上限就是 CPU 能同時執行的硬體執行緒數。要算清楚,得先分清楚「核心」的兩種意思。

實體核心 vs 邏輯核心(虛擬核心)

名詞 是什麼
實體核心(Physical Core) 晶片上真正的運算單元
邏輯核心(Logical Core / 虛擬核心) Hyper-Threading(Intel)/ SMT(AMD) 技術,讓一個實體核心對 OS 假裝成兩個,共用同一套執行單元
4 實體核心 + Hyper-Threading(每核 2 執行緒)
   = 8 邏輯核心
   → 作業系統看到的是「8 顆 CPU」,排程也以邏輯核心為對象

Hyper-Threading / SMT 的真相

一個實體核心開兩個邏輯核心,不等於兩倍效能。兩條硬體執行緒共用同一核心的執行單元、快取,只是趁某條在等記憶體時讓另一條補上空檔。

  • 實際增益常見約 1.2~1.3 倍(視工作負載),不是 2 倍
  • CPU 全速計算(無等待)時,超執行緒幫助有限
  • 所以「8 邏輯核心」的並行實力 < 「8 個真實體核心」

「虛擬核心」的兩個意思

別混淆——這個詞有兩種常見用法:

  • 桌機 / 伺服器 CPU:通常指 Hyper-Threading 的邏輯核心
  • 雲端 / 虛擬機:常指 vCPU——Hypervisor 分配給 VM 的核心(一個 vCPU 多半對應一條實體機的邏輯核心,但會被多台 VM 共享、超賣)

怎麼查有幾核

# Linux
nproc                 # 邏輯核心數(OS 可用的)
lscpu                 # 看 Socket / Core / Thread per core 詳情

# macOS
sysctl -n hw.physicalcpu   # 實體核心
sysctl -n hw.logicalcpu    # 邏輯核心

執行緒數該設多少?

執行緒數的設定以邏輯核心數為基準,再依工作型態調整:

工作型態 建議執行緒數 原因
CPU 密集(運算為主,少等待) ≈ 邏輯核心數(或 +1) 再多也沒核心可跑,只增加切換
I/O 密集(大量等網路 / 磁碟) 遠多於核心數 多數執行緒在等,可超開填滿等待空檔

常用的經驗公式(Brian Goetz):

最佳執行緒數 ≈ 邏輯核心數 × (1 + 等待時間 / 計算時間)

純計算(等待≈0)→ ≈ 核心數
等待是計算的 3 倍 → ≈ 核心數 × 4

關鍵直覺:CPU 密集靠「核心數」設上限(多了只是徒增情境切換);I/O 密集才值得超開,因為大部分執行緒都卡在等待、沒佔用核心。這也呼應了執行緒模型筆記裡 M:N 模型為何適合海量 I/O。


搶佔式 vs 協作式排程

決定「執行緒何時讓出 CPU」的兩種策略:

搶佔式(Preemptive)

OS 可以強制中斷正在執行的執行緒(時間片用完、或更高優先權者就緒),收回 CPU 交給別人。

  • 現代通用 OS(Linux、Windows、macOS)都是搶佔式
  • 優點:單一執行緒無法霸佔 CPU,公平、回應快
  • 代價:需要時鐘中斷與情境切換

協作式(Cooperative / Non-preemptive)

執行緒主動讓出才會切換(如執行到 yield、等 I/O)。OS 不強制收回。

  • 早期系統、部分協程排程採用
  • 優點:切換點明確、開銷小
  • 缺點:一條不讓出(無窮迴圈)就會卡死全部
搶佔式:時間到 → OS 強制換人(不管你願不願意)
協作式:你跑完 / 主動 yield → 才換人(不讓就霸佔)

協程(coroutine)多是協作式:在明確的 await / yield 點切換。


時間片與排程器

時間片(Time Slice / Quantum)

搶佔式系統給每條執行緒一段最長連續執行時間(時間片,通常數毫秒)。用完就被搶佔、回到就緒佇列排隊。

  • 時間片太長 → 回應變慢(別人等太久)
  • 時間片太短 → 情境切換太頻繁,開銷吃掉效能

優先權(Priority)

排程器通常依優先權決定先後;高優先權執行緒先取得 CPU。需處理飢餓(starvation)——低優先權者長期搶不到,常用「老化(aging)」逐步提升等待者優先權。

現代排程器舉例

  • Linux CFS(Completely Fair Scheduler):以「虛擬執行時間」追求公平,讓每條 task 取得近似等比的 CPU 時間(較新核心引入 EEVDF 排程器)。

常見排程演算法

演算法 規則 特點
FCFS(先到先服務) 依到達順序 簡單;長任務會拖住後面(護航效應)
SJF(最短工作優先) 先跑預估最短的 平均等待短;需預估、可能飢餓
Round Robin(輪詢) 每人一個時間片輪流 公平、回應好;切換開銷
Priority(優先權) 高優先權先 重要任務優先;需防飢餓
Multilevel Feedback Queue 多層佇列 + 動態調整優先權 兼顧互動與批次,通用 OS 常用

通用桌面 / 伺服器 OS 多採類似「多層回饋佇列 / 公平排程」的混合策略,而非單一純演算法。


情境切換(Context Switch)

當 CPU 從一條執行緒切到另一條時,必須保存舊的、載入新的執行狀態,這個動作叫情境切換。

切換時保存 / 還原什麼

  • 暫存器、程式計數器(PC)、堆疊指標
  • 跨行程切換:還要切換位址空間(page table)、刷新 TLB
Thread A 執行中
  │ 時間片用完 / 等 I/O / 被搶佔
保存 A 的狀態 → 排程器選 B → 載入 B 的狀態 → B 執行
       └──────── 這段純開銷,沒做有用工作 ────────┘

成本

  • 直接成本:保存 / 載入狀態的 CPU 週期
  • 間接成本:快取(cache)與 TLB 失效——新執行緒的資料不在快取裡,要重新載入,往往比直接成本更傷
  • 同行程內執行緒切換跨行程切換便宜(不必換位址空間 / 刷 TLB)

情境切換是「純開銷」:這段時間 CPU 沒在做應用的有用工作。過多執行緒 / 過度切換會讓 context switch 吃掉效能——這也是「執行緒不是越多越好」的根本原因。


實務觀察與調校

觀察情境切換

vmstat 1          # cs 欄 = 每秒 context switches;in = 中斷數
pidstat -w 1      # 每個行程的自願/非自願切換次數
  • 自願切換(voluntary):主動等 I/O / 鎖造成
  • 非自願切換(involuntary):時間片用完被搶佔,數值過高可能代表 CPU 競爭激烈或執行緒過多

調校方向

  • 執行緒數量設定貼近核心數(CPU 密集約 = 核心數;I/O 密集可更多),避免過度切換
  • 減少鎖競爭(降低自願切換與等待)
  • 善用執行緒池,避免頻繁建立 / 銷毀執行緒

監控指標細節見 作業系統效能監控指標


常見問題

問題 1:並發和並行差在哪?

並發是「同一段時間內處理多件事」(單核靠切換也行);並行是「同一時刻真的同時執行」(需多核)。並發是結構,並行是執行表現。

問題 2:單核 CPU 能多執行緒嗎?

能。單核透過快速切換達成並發,多條執行緒交錯推進,但無法並行(同一時刻只有一條真的在跑)。

問題 3:搶佔式和協作式哪個好?

通用 OS 用搶佔式,避免單一執行緒霸佔、回應更穩。協作式切換開銷小但風險高(一條不讓出就卡死全部),多見於協程排程。

問題 4:為什麼執行緒不是越多越好?

執行緒超過核心數太多時,情境切換鎖競爭的開銷會抵銷並發收益,甚至更慢。CPU 密集型尤其明顯。

問題 5:context switch 數值多少算高?

沒有絕對值,要看基準與趨勢。重點看非自願切換是否異常攀升、是否伴隨 CPU 高使用率與效能下降,再對照執行緒數調整。

問題 6:實體核心和邏輯核心(虛擬核心)差在哪?執行緒數要看哪個?

實體核心是真正的運算單元;邏輯核心是 Hyper-Threading / SMT 讓一個實體核心對 OS「假裝」成的兩個,共用執行單元(增益約 1.2~1.3 倍,非 2 倍)。OS 排程與執行緒數設定都以邏輯核心數為基準nproc / sysctl hw.logicalcpu),再依 CPU 密集(≈ 核心數)或 I/O 密集(可超開)調整。


總結

核心要點

  • CPU 排程決定哪條執行緒、在哪核心、跑多久;執行緒是排程的基本單位
  • 並發=同段時間處理多事(單核可);並行=同刻真的同時(需多核)
  • 現代 OS 用搶佔式 + 時間片,避免霸佔;協程多是協作式
  • 演算法:FCFS / SJF / RR / Priority / 多層回饋佇列,通用 OS 採混合公平策略
  • 情境切換保存/載入狀態,是純開銷;快取 / TLB 失效是隱性成本
  • 執行緒數應貼近核心數,過度切換反而拖慢

快速參考

一句話
並發 同段時間處理多事(結構)
並行 同刻同時執行(需多核)
搶佔式 OS 強制收回 CPU
協作式 執行緒主動讓出
時間片 一次最長連續執行時間
情境切換 換執行緒時存/載狀態的開銷

建立日期:2026-06-18

🔗相關文章