傳統阻塞 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
                    └─────────────────────┘

流程:

  1. Event Loop 用 OS 的 epoll(Linux)/ kqueue(macOS)/ IOCP(Windows)監聽所有 I/O
  2. 某個連線的資料就緒時,OS 通知 Event Loop
  3. 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)。

ReactorProactor
通知時機I/O 就緒(可以讀了)I/O 完成(資料已在 buffer)
OS 支援epoll / kqueueWindows IOCP / Linux io_uring
代表Node.js / nginx / NettyWindows 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 裡。