檔案與輸入輸出

阻塞 vs 非阻塞 I/O

你打電話到店裡問訂單好了沒。一種風格是店員說「請稍候」,讓你靜靜等到答案備妥——這期間你什麼別的都做不了。另一種風格是店員說「還沒,晚點再打來」,放你去做別的事。前者是阻塞;後者是非阻塞。

具體來說,預設情況下 read() 或 write() 是阻塞的:若一次讀取目前沒有可用資料——鍵盤沉默、管線是空的、網路還沒送達——這個呼叫就乾脆不返回;你的程式被暫停(核心把它標記為不可執行),直到資料抵達。當「等待」正是你要的時,這沒問題,而且暫停期間不耗 CPU。非阻塞 I/O 改變了這個約定:你用 O_NONBLOCK 旗標開啟檔案(或之後用 fcntl 設定)。現在若一次讀取本來得等,它會立刻回傳 -1 並把 errno 設為 EAGAIN(或 EWOULDBLOCK),意思是「現在什麼都沒有,晚點再試」,而一次無法進行的寫入也一樣。你的程式保持掌控,可以去做別的有用工作,之後再回來重試。

為何重要:阻塞很簡單,對於一次只做一件事的程式很完美。但一個同時雜耍許多連線的程式——一台有數百個用戶端的聊天伺服器——可承受不起為了一個慢吞吞的讀取者而凍結、害其餘的餓著。非阻塞 I/O 是同時處理許多描述符的基礎,通常搭配一個就緒機制(select、poll、epoll),它告訴你哪些描述符可以在不阻塞的情況下讀或寫,於是你只去碰那些已就緒的。代價是更複雜的程式碼:你必須到處處理 EAGAIN,並管理自己的迴圈。

int fd = open("pipe", O_RDONLY | O_NONBLOCK); ssize_t n = read(fd, buf, sizeof buf); if (n < 0 && errno == EAGAIN) { /* 現在沒東西就緒——去做別的工作,晚點重試 */ }

有了 O_NONBLOCK,一個本來會阻塞的讀取會回傳 EAGAIN,而非暫停程式。

EAGAIN/EWOULDBLOCK 不是真正的錯誤——它意思是「再試一次」,你必須處理它,通常搭配一個就緒呼叫(poll/select/epoll),才不會空轉迴圈。被阻塞的行程在等待時耗用零 CPU;而天真的非阻塞重試迴圈,可能白白燒掉一整顆核心 100% 的算力。

又称
blocking and non-blocking readsO_NONBLOCK阻塞與非阻塞讀寫