非同步輸入輸出(asynchronous I/O)
點外送有三種方式。阻塞式是打電話然後一直掛在線上等,在食物備好前什麼都不做。非阻塞式是打過去、被告知「還沒好,再打來」,於是你一邊做別的事一邊反覆查詢。非同步式是留下你的號碼:你掛斷、繼續過你的一天,等送到時他們打給你。這三種風格——阻塞、非阻塞、非同步——正是程式等待輸入輸出的方式,而非同步輸入輸出就是「留下號碼」那一種。
具體來說,對照一下。一個阻塞式的 read(fd, buf, n) 在資料進到 buf 之前不會返回;呼叫的執行緒同時入睡,這很簡單,卻讓那條執行緒停滯。一個非阻塞式的 read 立即返回,帶回資料或一個「會阻塞」的狀態,所以程式必須持續輪詢或再問一次。非同步輸入輸出更進一步:程式提交請求後呼叫立刻返回,作業系統在背景進行傳輸,完成稍後才被回報——透過回呼、一個信號,或由程式去問一個事件通知介面(select、poll、epoll、io_uring),看它眾多未完成的操作中哪些已經結束。核心觀念是重疊:在輸入輸出進行中時,有用的運算、或對成千上萬條其他連線的管理,都同時進行。
為什麼重要:非同步輸入輸出是單一執行緒能同時兼顧大量緩慢操作的方法——是處理數萬條網路連線而不必一連線一執行緒的高效能伺服器的基礎。它也是三種風格中最複雜的,因為程式必須追蹤未完成的請求、並亂序處理完成,這較難推理、也較難寫對。誠實的定位:阻塞式最簡單、往往也完全夠用;當你真的需要一條執行緒同時等待許多東西時,才動用非同步輸入輸出。
一個網頁伺服器告訴作業系統「開始從這 5000 條通訊端讀取」,然後繼續執行。它不在任何一條上阻塞;當資料抵達第 837 條通訊端時,作業系統(透過 epoll)通知它,它便只處理那一條——用單一執行緒服務數千個客戶端。
提交、繼續、稍後被告知哪些操作完成了——沒有執行緒閒坐著。
非同步不等於非阻塞:非阻塞仍要你一問再問,而非同步是由作業系統在完成時通知你。而且非同步並不讓單一次傳輸變快——它讓你把許多次傳輸與其他工作重疊起來。