Go語言 搶占式調(diào)度

2018-07-24 18:29 更新

goroutine本來是設(shè)計(jì)為協(xié)程形式,但是隨著調(diào)度器的實(shí)現(xiàn)越來越成熟,Go在1.2版中開始引入比較初級(jí)的搶占式調(diào)度。

從一個(gè)bug說起

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)。

總體思路

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

只有長時(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)度還是很初級(jí)的,比如一個(gè)goroutine運(yùn)行了很久,但是它并沒有調(diào)用另一個(gè)函數(shù),則它不會(huì)被搶占。當(dāng)然,一個(gè)運(yùn)行很久卻不調(diào)用函數(shù)的代碼并不是多數(shù)情況。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)