傳統阻塞 I/O 的問題
Thread 1: → [建立 socket] → [等待資料...] → [讀資料] → [處理] → [回覆]
Thread 2: → [建立 socket] → [等待資料...] → [讀資料] → ...
每個連線佔用一個 thread,thread 大部分時間在「等待資料」(阻塞)。1000 個並發連線 = 1000 個 thread,大量記憶體和 context switch overhead。
Reactor Pattern(同步非阻塞)
Reactor 的核心思想:不讓 thread 阻塞等待 I/O,改成「當 I/O 就緒時通知我」。
┌─────────────────────┐
│ Event Loop │
連線 A ──→ I/O │ Selector (epoll/ │
連線 B ──→ I/O ──→ │ kqueue/IOCP) 監聽 │──→ Handler A
連線 C ──→ I/O │ 哪些 I/O 就緒 │──→ Handler B
└─────────────────────┘
流程:
- Event Loop 用 OS 的
epoll(Linux)/kqueue(macOS)/IOCP(Windows)監聽所有 I/O - 某個連線的資料就緒時,OS 通知 Event Loop
- Event Loop 呼叫對應的 Handler 處理
Node.js 的 event loop、nginx 的 worker process、Java Netty 的 EventLoop 都是 Reactor Pattern 的實作。
// Node.js:Reactor Pattern 的語言體現
// 你寫的 async/await 和 callback 就是 Handler
http.createServer((req, res) => {
// 這個 callback 就是 Handler,在 I/O 就緒時被 event loop 呼叫
fs.readFile('./data.json', (err, data) => {
res.end(data);
});
});Proactor Pattern(非同步 I/O)
Reactor 是「I/O 就緒後通知你,你來讀資料」(同步 I/O,只是非阻塞)。Proactor 是「我幫你讀完資料,讀完了再通知你」(真正的異步 I/O)。
| Reactor | Proactor | |
|---|---|---|
| 通知時機 | I/O 就緒(可以讀了) | I/O 完成(資料已在 buffer) |
| OS 支援 | epoll / kqueue | Windows IOCP / Linux io_uring |
| 代表 | Node.js / nginx / Netty | Windows IOCP / C++ Boost.Asio |
Linux 的 io_uring(2019+)讓 Proactor 在 Linux 上成為現實,是 Reactor 的下一代。
多 Reactor(Multi-Reactor / Netty 架構)
單一 Reactor 遇到大量連線時仍然有 bottleneck。現代高性能框架用多個 Reactor:
MainReactor(少數 thread)
├── 接受新連線
└── 分發到 SubReactor
SubReactor(多個 thread)
├── 負責已建立連線的 I/O 事件
└── 呼叫 Worker 處理業務邏輯
Worker Pool(執行業務邏輯)
Java Netty 的 NioEventLoopGroup 就是這個架構:bossGroup 是 MainReactor,workerGroup 是 SubReactor。
和 async/await 的關係
async/await 是 Reactor Pattern 的語法糖——讓你用同步的寫法寫非同步的邏輯,背後仍然是 event loop 的 Handler 機制。理解 Reactor,就理解了 await 為什麼不阻塞 thread,以及為什麼 CPU 密集的任務要放到 worker thread pool 裡。