golang 搶占式調(diào)度

這里說的 “golang 搶占式調(diào)度”,只是 goroutine 調(diào)度的一部分內(nèi)容。個(gè)人對 go 的調(diào)度理解為:偽搶占 + working steal。


起因

Go在設(shè)計(jì)之初并沒考慮將goroutine設(shè)計(jì)成搶占式的。用戶負(fù)責(zé)讓各個(gè)goroutine交互合作完成任務(wù)。一個(gè)goroutine只有在涉及到加鎖,讀寫通道或者主動(dòng)讓出CPU等操作時(shí)才會(huì)觸發(fā)切換。

垃圾回收器是需要stop the world的。如果垃圾回收器想要運(yùn)行了,那么它必須先通知其它的goroutine合作停下來,這會(huì)造成較長時(shí)間的等待時(shí)間??紤]一種很極端的情況,所有的goroutine都停下來了,只有其中一個(gè)沒有停,那么垃圾回收就會(huì)一直等待著沒有停的那一個(gè)。

搶占式調(diào)度可以解決這種問題,在搶占式情況下,如果一個(gè)goroutine運(yùn)行時(shí)間過長,它就會(huì)被剝奪運(yùn)行權(quán)。

于是,Go在1.2版中開始引入比較初級的搶占式調(diào)度。

總體思路

引入搶占式調(diào)度,會(huì)對最初的設(shè)計(jì)產(chǎn)生比較大的影響,Go還只是引入了一些很初級的搶占,并沒有像操作系統(tǒng)調(diào)度那么復(fù)雜,沒有對goroutine分時(shí)間片,設(shè)置優(yōu)先級等。

只有長時(shí)間阻塞于系統(tǒng)調(diào)用,或者運(yùn)行了較長時(shí)間才會(huì)被搶占。runtime會(huì)在后臺(tái)有一個(gè)檢測線程,它會(huì)檢測這些情況,并通知goroutine執(zhí)行調(diào)度。

目前并沒有直接在后臺(tái)的檢測線程中做處理調(diào)度器相關(guān)邏輯,只是相當(dāng)于給goroutine加了一個(gè)“標(biāo)記”,然后在它進(jìn)入函數(shù)時(shí)才會(huì)觸發(fā)調(diào)度。這么做應(yīng)該是出于對現(xiàn)有代碼的修改最小的考慮。

sysmon

前面講Go程序的初始化過程中有提到過,runtime開了一條后臺(tái)線程,運(yùn)行一個(gè)sysmon函數(shù)。這個(gè)函數(shù)會(huì)周期性地做epoll操作,同時(shí)它還會(huì)檢測每個(gè)P是否運(yùn)行了較長時(shí)間。

如果檢測到某個(gè)P狀態(tài)處于Psyscall超過了一個(gè)sysmon的時(shí)間周期(20us),并且還有其它可運(yùn)行的任務(wù),則切換P。

如果檢測到某個(gè)P的狀態(tài)為Prunning,并且它已經(jīng)運(yùn)行了超過10ms,則會(huì)將P的當(dāng)前的G的stackguard設(shè)置為StackPreempt。這個(gè)操作其實(shí)是相當(dāng)于加上一個(gè)標(biāo)記,通知這個(gè)G在合適時(shí)機(jī)進(jìn)行調(diào)度。

目前這里只是盡最大努力送達(dá),但并不保證收到消息的goroutine一定會(huì)執(zhí)行調(diào)度讓出運(yùn)行權(quán)。

morestack的修改

前面說的,將stackguard設(shè)置為StackPreempt實(shí)際上是一個(gè)比較trick的代碼。我們知道Go會(huì)在每個(gè)函數(shù)入口處比較當(dāng)前的棧寄存器值和stackguard值來決定是否觸發(fā)morestack函數(shù)。

將stackguard設(shè)置為StackPreempt作用是進(jìn)入函數(shù)時(shí)必定觸發(fā)morestack,然后在morestack中再引發(fā)調(diào)度。

看一下StackPreempt的定義,它是大于任何實(shí)際的棧寄存器的值的:

// 0xfffffade in hex.
#define StackPreempt ((uint64)-1314)

然后在morestack中加了一小段代碼,如果發(fā)現(xiàn)stackguard為StackPreempt,則相當(dāng)于調(diào)用runtime.Gosched。

所以,到目前為止Go的搶占式調(diào)度還是很初級的,比如一個(gè)goroutine運(yùn)行了很久,但是它并沒有調(diào)用另一個(gè)函數(shù),則它不會(huì)被搶占。當(dāng)然,一個(gè)運(yùn)行很久卻不調(diào)用函數(shù)的代碼并不是多數(shù)情況。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容