協程(coroutine)
/ CO-roo-teen /
想像你在讀一本「選擇你自己的冒險」書,你可以夾上書籤、把書放下去做別的事,稍後再從你離開的地方接著讀,就在句子中間,而你記得的一切都原封不動。協程就是能做到這件事的函式:它可以在執行到一半時暫停自己、把控制權交還給呼叫它的人,稍後再從它暫停的那一點繼續,所有的區域變數都還在原位。普通函式只懂兩個動作,開始與返回;協程多加了暫停與恢復。
具體來說,普通函式一返回,它的區域狀態就消失了。協程則是讓位(yield):它凍結自己的位置與區域變數、交還控制權,被恢復時再從那個凍結點繼續。這使得協程成為撰寫「會等待」的程式碼的自然方式,等輸入、等網路回覆,而不必阻塞一整條執行緒。與其讓一條執行緒閒著卡在緩慢的讀取上,協程在等待處讓位、讓其他協程去跑、等資料就緒時再被恢復。關鍵在於,協程是協作式排程的:一個協程會一直跑到它自己選擇讓位為止,沒有東西能搶佔它。這正是現代 async/await 背後的引擎:一個 await 點,就是協程可能暫停、讓其他工作得以進行的地方。
它的定位:協程是綠色執行緒與非同步任務底下的建構單元,對於 I/O 密集、大多在等待、跑在單一執行緒上的工作負載特別出色,而且因為暫停點是明確標出的,切換比使用者層級執行緒還要便宜。誠實的提醒是協作式排程的另一面,由於協程只有在它自己決定時才讓位,一個跑著長時間 CPU 密集迴圈卻不讓位的協程,會霸佔執行緒、讓其他所有協程飢餓。協程在單一執行緒上給你便宜的並行;要真正的平行,底下仍然需要好幾條執行緒或核心。
使用 async/await,一個下載函式可以寫成:發出請求,然後 await 回覆。在那個 await 處,協程暫停,程式改去跑其他協程而不阻塞執行緒;回覆抵達時,協程在 await 之後那一行恢復,彷彿從未暫停過。
一個能暫停又恢復的函式,就是 async/await 底下的引擎。
協程是協作式排程的:一個永不讓位的協程會獨佔它的執行緒、讓其餘飢餓。它們提供便宜的單一執行緒並行,而非平行,平行仍需要多條執行緒或核心。