本文盡量通俗易懂地講Go調(diào)度器(scheduler)的相關(guān)知識奴饮,尤其是普通開發(fā)者能夠關(guān)注和控制的部分纬向。調(diào)度器本身十分復(fù)雜择浊,所以下文難免有疏漏戴卜,發(fā)現(xiàn)后會盡量及時更新。
要點
- go程序的運(yùn)行琢岩,以goroutine為單位投剥,而goroutine實際運(yùn)行在某個系統(tǒng)線程內(nèi)。goroutine(可以非常多)和系統(tǒng)線程(相對比較少)并非一一對應(yīng)担孔。調(diào)度時江锨,既有os調(diào)度線程,也有g(shù)o調(diào)度器本身調(diào)度goroutine糕篇。簡言之啄育,go原生支持并發(fā),go調(diào)度器負(fù)責(zé)將各個goroutine調(diào)度到不同的操作系統(tǒng)線程中取執(zhí)行拌消。
- 三個定義:
- G: goroutine挑豌,就是平常提到的go中的協(xié)程
- P: process,處理器墩崩,有的文章說代表上下文氓英,也可以理解為附帶有上下文信息的令牌
- M: machine,線程鹦筹,就是平常提到的操作系統(tǒng)中的線程
- Go早期是GM模型铝阐,后來因為性能問題轉(zhuǎn)而使用GPM模型
- 執(zhí)行機(jī)制:
- M綁定P,才可以不斷去運(yùn)行(不同的可執(zhí)行的)G铐拐,可搶占式調(diào)度(靠sysmon)
- P有自己的G隊列(無鎖訪問徘键,快);同時遍蟋,程序也有一個全局的G隊列
- M執(zhí)行一些系統(tǒng)調(diào)用的時候吹害,可能會與P解除綁定;M也可能休眠
- M, P, G 三者數(shù)量各異匿值,M默認(rèn)10000(SetMaxThreads更改赠制,一般不用),P默認(rèn)是機(jī)器CPU核數(shù)(可由
GOMAXPROCS
指定)挟憔,G沒有明確限制(通過go指令創(chuàng)建)钟些。
細(xì)節(jié)
-
GM到GPM
早期,GM模型有諸多問題绊谭,例如全局鎖政恍,M緩存內(nèi)存占用浪費(fèi)等,詳見《Scalable Go Scheduler Design》达传。因此篙耗,大神操刀加了一層中間層(P)迫筑,調(diào)度模型變成GPM,沿用至今(盜圖一張):
通俗地講宗弯,G要運(yùn)行脯燃,需要綁定一個P(放在P的本地隊列里),然后由與P綁定的操作系統(tǒng)線程M真正執(zhí)行蒙保。
G切換時辕棚,只是M從G1切到G2而已,都是在用戶態(tài)進(jìn)行著邓厕,非常輕量逝嚎,不像操作系統(tǒng)切換M時比較重。
P的本地隊列中缺少G時详恼,會從其他P的隊列里“偷”一些或者從全局隊列里取补君。
借助于netpoller,發(fā)起網(wǎng)絡(luò)調(diào)用時昧互,G阻塞挽铁,M不阻塞,切換G即可硅堆。而發(fā)起文件IO等操作時屿储,會執(zhí)行(阻塞的)系統(tǒng)調(diào)用,(注:現(xiàn)在應(yīng)該實現(xiàn)了部分poller for os package)渐逃,此時M也會等待系統(tǒng)調(diào)用的返回够掠。M和G一起,會解除與P的綁定茄菊。如果P的本地隊列還有其他G疯潭,就會綁定另外一個空閑的M,如果沒有面殖,則新建一個M竖哩,然后繼續(xù)執(zhí)行可以執(zhí)行的G。 調(diào)度器實現(xiàn)了搶占
也就是說如果一個G執(zhí)行太久脊僚,是會被切換出去的相叁。
這樣可以確保整個程序看起來是“并發(fā)”執(zhí)行的,而不是一個G可以執(zhí)行時就是一直執(zhí)行辽幌,其他G都餓死增淹。
但是切換點需要是函數(shù)調(diào)用。假設(shè)G中是不調(diào)函數(shù)的純無限循環(huán)計算乌企,還是無法被搶占虑润。什么時候G會被調(diào)度
- 被sysmon設(shè)置為搶占
- channel阻塞或網(wǎng)絡(luò)IO
- mutex等同步導(dǎo)致阻塞
- 使用go關(guān)鍵字創(chuàng)建goroutine
- GC過程中各種策略導(dǎo)致的調(diào)度
runtime中,網(wǎng)絡(luò)IO的實現(xiàn)采用了kqueue (MacOS), epoll (Linux)或iocp (Windows) 加酵。
查看各種調(diào)度狀態(tài)
執(zhí)行命令的時候拳喻,設(shè)置GODEBUG
環(huán)境變量哭当。例如:GODEBUG=schedtrace=1000,scheddetail=1 godoc -http=:6060
P有l(wèi)ocal隊列的好處
其實好處有好幾點。比較明顯的是冗澈,GM模型里面钦勘,M切換G時,需要從全局隊列里面取渗柿,需要加鎖个盆。GPM中,M綁定著P朵栖,M切換的G都在P的本地G隊列中,不需要鎖柴梆。P默認(rèn)是機(jī)器邏輯核數(shù)
因為超線程技術(shù)的存在陨溅,邏輯核數(shù)會與物理核數(shù)不同。下面的語句可以打印出邏輯核數(shù)绍在,通過GOMAXPROCS
設(shè)置時门扇,可別弄錯了
fmt.Println(runtime.NumCPU())
- M默認(rèn)是10000
M對應(yīng)的是sched.maxmcount
,默認(rèn)10000偿渡。通過SetMaxThreads
可修改臼寄,如果程序使用超過這個數(shù),會自動crash溜宽!
// 改動時也會檢查吉拳,并不能隨意設(shè)置值
if in > 0x7fffffff { // MaxInt32
sched.maxmcount = 0x7fffffff
} else {
sched.maxmcount = int32(in)
}
- 相關(guān)代碼片段(不詳細(xì)研究的可以不用看了)
參見下一篇吧,這篇已很長了适揉。
參考鏈接: