Process(進程)
Process 是 OS 分配資源的最小單位——每個 Process 有自己的記憶體空間、文件描述符、資源。
Process A Process B
┌──────────────┐ ┌──────────────┐
│ Memory Space │ │ Memory Space │ ← 隔離的
│ File handles │ │ File handles │
│ Stack │ │ Stack │
└──────────────┘ └──────────────┘
優點:完全隔離,一個 Process crash 不影響其他。
缺點:建立 Process 的成本高(fork 需要複製記憶體);Process 之間通信(IPC)複雜(管道、socket、共享記憶體)。
適用:需要強隔離的場景(Docker container 就是 process 隔離);CPU 密集型任務利用多核(Python 用 multiprocessing 繞過 GIL)。
Thread(線程)
Thread 是同一個 Process 裡的執行單位,共享 Process 的記憶體空間。
Process
┌──────────────────────────────────┐
│ Memory Space(共享) │
│ File handles(共享) │
│ Thread 1: Stack + Program Counter │
│ Thread 2: Stack + Program Counter │
└──────────────────────────────────┘
優點:比 Process 輕量(不需要複製記憶體);共享記憶體讓 Thread 間通信容易。
缺點:共享記憶體帶來 race condition 和 deadlock 的風險;需要鎖機制(mutex、semaphore)。
Context Switch 成本:OS 切換 Thread 時需要保存和恢復 CPU 暫存器、stack。一個 Thread 大約 1MB stack,一個 process 裡幾千個 thread 就會有記憶體壓力和大量 context switch 開銷。
Coroutine(協程)
Coroutine 是在用戶空間(不是 OS kernel)管理的「輕量 thread」,由程式(而不是 OS)決定什麼時候切換。
OS Thread 1
└── Event Loop
├── Coroutine A: running...
│ await io_operation() → 讓出執行權
│
├── Coroutine B: running... ← Event loop 接手
│ await io_operation() → 讓出執行權
│
└── Coroutine A: resumed ← I/O 完成,繼續
優點:
- 成本極低(Go 的 goroutine 初始只需 ~2KB)
- 同一個 OS thread 可以跑幾萬甚至幾十萬個 coroutine
- 沒有真正的並行(預設單 thread),所以沒有 race condition(對 I/O 密集型)
缺點:
- 需要 async 相容的 I/O 庫(同步的 I/O 會阻塞整個 event loop)
- CPU 密集型任務仍需要多 thread 或多 process
三者的選擇
| Process | Thread | Coroutine | |
|---|---|---|---|
| 隔離性 | 強 | 弱 | 弱 |
| 記憶體開銷 | 高 | 中(~1MB/thread) | 低(~2KB/goroutine) |
| 建立成本 | 高 | 中 | 低 |
| 通信 | IPC(複雜) | 共享記憶體(有 race 風險) | await/channel |
| 適合 | CPU 密集、強隔離 | CPU 密集 + 共享狀態 | I/O 密集、高並發 |
Go 的 goroutine 是 coroutine 的實作,但 Go runtime 會把 goroutine 分配到多個 OS thread——同時有 coroutine 的輕量和多核的 CPU 利用率。
Node.js / Python asyncio 是 coroutine 但只用單 OS thread——適合 I/O 密集,CPU 密集需要 worker_threads / multiprocessing。
Java virtual thread(JDK 21):JVM 層的 coroutine 實作,讓你用同步的語法寫,底層自動轉為非阻塞。