行程間通訊

就緒與背壓(readiness and backpressure)

有兩個概念位於「行程如何搬運資料而不互相淹沒」的核心。第一是就緒:知道一個操作何時能進行而不必讓你等待。第二是背壓:當一方產生資料的速度快過另一方能消耗的速度時,會發生什麼、又告訴了你什麼。兩者合起來回答「我何時能動作,又是什麼阻止我壓垮另一端?」

先從阻塞與非阻塞講起,因為它塑造了這兩者。阻塞呼叫會等到工作能進行為止:對空管線做 read() 會睡到有資料到;對滿管線做 write() 會睡到空間騰出。非阻塞呼叫(用 O_NONBLOCK 旗標設定)從不等待——若它此刻無法進行,就立刻以 EAGAIN/EWOULDBLOCK 返回,意思是「尚未就緒,稍後再試」。非阻塞模式正是跟 select()/poll() 搭配的:你只在多工器說某描述符就緒後才嘗試操作,於是你從不入睡。如此一來,就緒就是核心告訴你「這個此刻不會阻塞」。

背壓是管線、通訊端與佇列裡那個低調而精彩的部分。每一條這樣的通道都有有限的緩衝區。當一個快速的生產者因消費者太慢而把緩衝區填滿,生產者的下一次 write() 就會阻塞(或回傳 EAGAIN)。這個停頓不是錯誤——它是「慢下來,讀取方跟不上」的信號,會自動把生產者節流到消費者的步調,不丟資料、也不讓記憶體無限制成長。在 shell 管線中,這正是為什麼 cat 一個巨大檔案 | 慢速過濾器 永遠不會把記憶體撐爆:每當慢速過濾器落後,cat 就被滿的管線暫停。誠實的提醒:背壓只在你尊重它時才保護你——忽略 EAGAIN 而空轉、或在緊迴圈裡狂讀,你就燒掉 CPU;處理不當一個永遠排不掉的阻塞寫入,你就死結。

阻塞: write(fd, buf, n); /* 管線滿就睡——天然背壓 */ 非阻塞: fcntl(fd, F_SETFL, O_NONBLOCK); n = write(fd, buf, n); if (n < 0 && errno == EAGAIN) { /* 未就緒:用 poll() 等待,稍後重試 */ }

滿的緩衝區阻塞寫入方不是失敗——它是流量控制,叫快的一方等慢的一方。

背壓是特性,不是錯誤:被阻塞的寫入代表通道正替你做流量控制。錯誤的做法是去對抗它——在 EAGAIN 上忙碌空轉浪費 CPU;正確的做法是用 poll()/epoll 等待就緒,再重試。

又称
backpressureflow controlblocking vs non-blocking IPC背壓流量控制