協作式 vs 搶佔式排程(cooperative vs preemptive scheduling)
若好幾條執行緒共享一個核心,必須有東西決定何時一條停下、另一條輪到。有兩種根本不同的政策。在協作式排程下,正在跑的執行緒會一直占著核心,直到它自願放手——它必須有禮貌地讓出。在搶佔式排程下,系統能在任何時刻強行暫停一條執行緒、把核心交給別人,不管那條執行緒願不願意。想像一場會議:協作式是每位發言者一直講到自己決定停下並把發言權交出去;搶佔式則是有位拿著計時器的主席,能在任何人話講到一半時打斷。
在協作式排程裡,執行緒透過在合理的時點讓出控制權來協作(例如當它本來要等待輸入時)。好處是簡單與可預測:一條執行緒在它自己的讓出點之間不被打斷地跑,所以它能假設不會在某個操作中途被換出去。致命的壞處是:一條行為不良或有錯誤、永不讓出的執行緒——比方說卡在無窮迴圈裡——會霸佔核心、凍結其他所有人。在搶佔式排程裡,核心用一個硬體計時器中斷週期性地奪回控制權、決定要不要切換執行緒,所以沒有任何單一執行緒能獨佔核心。好處是穩健——一條卡住的執行緒無法凍結整個系統——代價是一條執行緒可能在任何一條指令處被暫停,毫無發言權。
這對我們的危害為何重要:現代作業系統的執行緒是搶佔式排程的,這正是為什麼你不能假設 count++ 會在切換前跑完——作業系統可能在操作中途搶佔你,造成交錯與競爭。在純協作式模型下,你有時能靠「在臨界區間裡不讓出」來避開鎖,但這個推理對搶佔式的作業系統執行緒「並不」成立,依賴它很危險。(協作式排程在使用者空間仍然興盛,它是 async/await 與「綠色執行緒」或協程的基礎——程式自己在已知時點之間切換的輕量任務,這也是它們能避開部分這類危害的原因之一。)
在協作式系統上,一條卡在 while (1) {} 的執行緒永不讓出,所以整台機器凍結。在搶佔式系統上,計時器中斷讓核心在那條執行緒的時間切片用完後暫停它、去跑別的——系統其餘部分仍保持回應。
協作式:自願讓出(簡單但脆弱)。搶佔式:被迫讓出(穩健,作業系統執行緒所用)。
對作業系統執行緒別依賴「我在這裡不會被打斷」——它們是搶佔式的,所以切換可發生在任何指令處。協作式的推理只適用於你掌控的使用者空間協程/非同步,不適用於由核心排程的執行緒。