Go調(diào)度器系列(2)宏觀看調(diào)度器

上一篇文章《Go語(yǔ)言高階:調(diào)度器系列(1)起源》,學(xué)goroutine調(diào)度器之前的一些背景知識(shí)痰驱,這篇文章則是為了對(duì)調(diào)度器有個(gè)宏觀的認(rèn)識(shí)既鞠,從宏觀的3個(gè)角度,去看待和理解調(diào)度器是什么樣子的朦肘,但仍然不涉及具體的調(diào)度原理饭弓。

三個(gè)角度分別是:

  1. 調(diào)度器的宏觀組成
  2. 調(diào)度器的生命周期
  3. GMP的可視化感受

在開(kāi)始前,先回憶下調(diào)度器相關(guān)的3個(gè)縮寫:

  • G: goroutine媒抠,每個(gè)G都代表1個(gè)goroutine
  • M: 工作線程弟断,是Go語(yǔ)言定義出來(lái)在用戶層面描述系統(tǒng)線程的對(duì)象 ,每個(gè)M代表一個(gè)系統(tǒng)線程
  • P: 處理器趴生,它包含了運(yùn)行Go代碼的資源阀趴。

3者的簡(jiǎn)要關(guān)系是P擁有G,M必須和一個(gè)P關(guān)聯(lián)才能運(yùn)行P擁有的G冲秽。

調(diào)度器的功能

《Go語(yǔ)言高階:調(diào)度器系列(1)起源》中介紹了協(xié)程和線程的關(guān)系舍咖,協(xié)程需要運(yùn)行在線程之上,線程由CPU進(jìn)行調(diào)度锉桑。

在Go中排霉,線程是運(yùn)行g(shù)oroutine的實(shí)體,調(diào)度器的功能是把可運(yùn)行的goroutine分配到工作線程上民轴。

Go的調(diào)度器也是經(jīng)過(guò)了多個(gè)版本的開(kāi)發(fā)才是現(xiàn)在這個(gè)樣子的攻柠,

  • 1.0版本發(fā)布了最初的、最簡(jiǎn)單的調(diào)度器后裸,是G-M模型瑰钮,存在4類問(wèn)題
  • 1.1版本重新設(shè)計(jì),修改為G-P-M模型微驶,奠定當(dāng)前調(diào)度器基本模樣
  • 1.2版本加入了搶占式調(diào)度浪谴,防止協(xié)程不讓出CPU導(dǎo)致其他G餓死

$GOROOT/src/runtime/proc.go的開(kāi)頭注釋中包含了對(duì)Scheduler的重要注釋开睡,介紹Scheduler的設(shè)計(jì)曾拒絕過(guò)3種方案以及原因,本文不再介紹了苟耻,希望你不要忽略為數(shù)不多的官方介紹篇恒。

Scheduler的宏觀組成

Tony Bai《也談goroutine調(diào)度器》中的這幅圖,展示了goroutine調(diào)度器和系統(tǒng)調(diào)度器的關(guān)系凶杖,而不是把二者割裂開(kāi)來(lái)胁艰,并且從宏觀的角度展示了調(diào)度器的重要組成。

image

自頂向下是調(diào)度器的4個(gè)部分:

  1. 全局隊(duì)列(Global Queue):存放等待運(yùn)行的G智蝠。
  2. P的本地隊(duì)列:同全局隊(duì)列類似腾么,存放的也是等待運(yùn)行的G,存的數(shù)量有限杈湾,不超過(guò)256個(gè)解虱。新建G'時(shí),G'優(yōu)先加入到P的本地隊(duì)列毛秘,如果隊(duì)列滿了饭寺,則會(huì)把本地隊(duì)列中一半的G移動(dòng)到全局隊(duì)列。
  3. P列表:所有的P都在程序啟動(dòng)時(shí)創(chuàng)建叫挟,并保存在數(shù)組中艰匙,最多有GOMAXPROCS個(gè)。
  4. M:線程想運(yùn)行任務(wù)就得獲取P抹恳,從P的本地隊(duì)列獲取G员凝,P隊(duì)列為空時(shí),M也會(huì)嘗試從全局隊(duì)列一批G放到P的本地隊(duì)列奋献,或從其他P的本地隊(duì)列一半放到自己P的本地隊(duì)列健霹。M運(yùn)行G,G執(zhí)行之后瓶蚂,M會(huì)從P獲取下一個(gè)G糖埋,不斷重復(fù)下去。

Goroutine調(diào)度器和OS調(diào)度器是通過(guò)M結(jié)合起來(lái)的窃这,每個(gè)M都代表了1個(gè)內(nèi)核線程瞳别,OS調(diào)度器負(fù)責(zé)把內(nèi)核線程分配到CPU的核上執(zhí)行

調(diào)度器的生命周期

接下來(lái)我們從另外一個(gè)宏觀角度——生命周期杭攻,認(rèn)識(shí)調(diào)度器祟敛。

所有的Go程序運(yùn)行都會(huì)經(jīng)過(guò)一個(gè)完整的調(diào)度器生命周期:從創(chuàng)建到結(jié)束。

image

即使下面這段簡(jiǎn)單的代碼:

package main

import "fmt"

// main.main
func main() {
    fmt.Println("Hello scheduler")
}

也會(huì)經(jīng)歷如上圖所示的過(guò)程:

  1. runtime創(chuàng)建最初的線程m0和goroutine g0兆解,并把2者關(guān)聯(lián)馆铁。
  2. 調(diào)度器初始化:初始化m0、棧锅睛、垃圾回收埠巨,以及創(chuàng)建和初始化由GOMAXPROCS個(gè)P構(gòu)成的P列表历谍。
  3. 示例代碼中的main函數(shù)是main.mainruntime中也有1個(gè)main函數(shù)——runtime.main辣垒,代碼經(jīng)過(guò)編譯后扮饶,runtime.main會(huì)調(diào)用main.main,程序啟動(dòng)時(shí)會(huì)為runtime.main創(chuàng)建goroutine乍构,稱它為main goroutine吧,然后把main goroutine加入到P的本地隊(duì)列扛点。
  4. 啟動(dòng)m0哥遮,m0已經(jīng)綁定了P,會(huì)從P的本地隊(duì)列獲取G陵究,獲取到main goroutine眠饮。
  5. G擁有棧,M根據(jù)G中的棧信息和調(diào)度信息設(shè)置運(yùn)行環(huán)境
  6. M運(yùn)行G
  7. G退出铜邮,再次回到M獲取可運(yùn)行的G仪召,這樣重復(fù)下去,直到main.main退出松蒜,runtime.main執(zhí)行Defer和Panic處理扔茅,或調(diào)用runtime.exit退出程序。

調(diào)度器的生命周期幾乎占滿了一個(gè)Go程序的一生秸苗,runtime.main的goroutine執(zhí)行之前都是為調(diào)度器做準(zhǔn)備工作召娜,runtime.main的goroutine運(yùn)行,才是調(diào)度器的真正開(kāi)始惊楼,直到runtime.main結(jié)束而結(jié)束玖瘸。

GMP的可視化感受

上面的兩個(gè)宏觀角度,都是根據(jù)文檔檀咙、代碼整理出來(lái)雅倒,最后我們從可視化角度感受下調(diào)度器,有2種方式弧可。

方式1:go tool trace

trace記錄了運(yùn)行時(shí)的信息蔑匣,能提供可視化的Web頁(yè)面。

簡(jiǎn)單測(cè)試代碼:main函數(shù)創(chuàng)建trace侣诺,trace會(huì)運(yùn)行在單獨(dú)的goroutine中殖演,然后main打印"Hello trace"退出。

func main() {
    // 創(chuàng)建trace文件
    f, err := os.Create("trace.out")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    // 啟動(dòng)trace goroutine
    err = trace.Start(f)
    if err != nil {
        panic(err)
    }
    defer trace.Stop()

    // main
    fmt.Println("Hello trace")
}

運(yùn)行程序和運(yùn)行trace:

?  trace git:(master) ? go run trace1.go
Hello trace
?  trace git:(master) ? ls
trace.out trace1.go
?  trace git:(master) ?
?  trace git:(master) ? go tool trace trace.out
2019/03/24 20:48:22 Parsing trace...
2019/03/24 20:48:22 Splitting trace...
2019/03/24 20:48:22 Opening browser. Trace viewer is listening on http://127.0.0.1:55984

效果:

trace1

從上至下分別是goroutine(G)年鸳、堆趴久、線程(M)、Proc(P)的信息搔确,從左到右是時(shí)間線彼棍。用鼠標(biāo)點(diǎn)擊顏色塊灭忠,最下面會(huì)列出詳細(xì)的信息。

我們可以發(fā)現(xiàn):

  • runtime.main的goroutine是g1座硕,這個(gè)編號(hào)應(yīng)該永遠(yuǎn)都不變的弛作,runtime.main是在g0之后創(chuàng)建的第一個(gè)goroutine。
  • g1中調(diào)用了main.main华匾,創(chuàng)建了trace goroutine g18映琳。g1運(yùn)行在P2上,g18運(yùn)行在P0上蜘拉。
  • P1上實(shí)際上也有g(shù)oroutine運(yùn)行萨西,可以看到短暫的豎線。

go tool trace的資料并不多旭旭,如果感興趣可閱讀:https://making.pusher.com/go-tool-trace/ 谎脯,中文翻譯是:https://mp.weixin.qq.com/s/nf_-AH_LeBN3913Pt6CzQQ

方式2:Debug trace

示例代碼:

// main.main
func main() {
    for i := 0; i < 5; i++ {
        time.Sleep(time.Second)
        fmt.Println("Hello scheduler")
    }
}

編譯和運(yùn)行持寄,運(yùn)行過(guò)程會(huì)打印trace:

?  one_routine2 git:(master) ? go build .
?  one_routine2 git:(master) ? GODEBUG=schedtrace=1000 ./one_routine2

結(jié)果:

SCHED 0ms: gomaxprocs=8 idleprocs=5 threads=5 spinningthreads=1 idlethreads=0 runqueue=0 [0 0 0 0 0 0 0 0]
SCHED 1001ms: gomaxprocs=8 idleprocs=8 threads=5 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0]
Hello scheduler
SCHED 2002ms: gomaxprocs=8 idleprocs=8 threads=5 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0]
Hello scheduler
SCHED 3004ms: gomaxprocs=8 idleprocs=8 threads=5 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0]
Hello scheduler
SCHED 4005ms: gomaxprocs=8 idleprocs=8 threads=5 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0]
Hello scheduler
SCHED 5013ms: gomaxprocs=8 idleprocs=8 threads=5 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0]
Hello scheduler

看到這密密麻麻的文字就有點(diǎn)擔(dān)心源梭,不要愁!因?yàn)槊啃凶侄味际且粯拥纳晕叮髯侄魏x如下:

  • SCHED:調(diào)試信息輸出標(biāo)志字符串废麻,代表本行是goroutine調(diào)度器的輸出;
  • 0ms:即從程序啟動(dòng)到輸出這行日志的時(shí)間仲闽;
  • gomaxprocs: P的數(shù)量脑溢,本例有8個(gè)P;
  • idleprocs: 處于idle狀態(tài)的P的數(shù)量赖欣;通過(guò)gomaxprocs和idleprocs的差值屑彻,我們就可知道執(zhí)行g(shù)o代碼的P的數(shù)量;
  • threads: os threads/M的數(shù)量顶吮,包含scheduler使用的m數(shù)量社牲,加上runtime自用的類似sysmon這樣的thread的數(shù)量;
  • spinningthreads: 處于自旋狀態(tài)的os thread數(shù)量悴了;
  • idlethread: 處于idle狀態(tài)的os thread的數(shù)量搏恤;
  • runqueue=0: Scheduler全局隊(duì)列中G的數(shù)量;
  • [0 0 0 0 0 0 0 0]: 分別為8個(gè)P的local queue中的G的數(shù)量湃交。

看第一行熟空,含義是:剛啟動(dòng)時(shí)創(chuàng)建了8個(gè)P,其中5個(gè)空閑的P搞莺,共創(chuàng)建5個(gè)M息罗,其中1個(gè)M處于自旋,沒(méi)有M處于空閑才沧,8個(gè)P的本地隊(duì)列都沒(méi)有G迈喉。

再看個(gè)復(fù)雜版本的绍刮,加上scheddetail=1可以打印更詳細(xì)的trace信息。

命令:

?  one_routine2 git:(master) ? GODEBUG=schedtrace=1000,scheddetail=1 ./one_routine2

結(jié)果:

image

截圖可能更代碼匹配不起來(lái)挨摸,最初代碼是for死循環(huán)孩革,后面為了減少打印加了限制循環(huán)5次

每次分別打印了每個(gè)P毙芜、M躯概、G的信息淑际,P的數(shù)量等于gomaxprocs探遵,M的數(shù)量等于threads,主要看圈黃的地方:

  • 第1處:P1和M2進(jìn)行了綁定邑茄。
  • 第2處:M2和P1進(jìn)行了綁定簇秒,但M2上沒(méi)有運(yùn)行的G溯泣。
  • 第3處:代碼中使用fmt進(jìn)行打印瞬女,會(huì)進(jìn)行系統(tǒng)調(diào)用,P1系統(tǒng)調(diào)用的次數(shù)很多努潘,說(shuō)明我們的用例函數(shù)基本在P1上運(yùn)行诽偷。
  • 第4處和第5處:M0上運(yùn)行了G1,G1的狀態(tài)為3(系統(tǒng)調(diào)用)疯坤,G進(jìn)行系統(tǒng)調(diào)用時(shí)报慕,M會(huì)和P解綁,但M會(huì)記住之前的P压怠,所以M0仍然記綁定了P1眠冈,而P1稱未綁定M。

總結(jié)時(shí)刻

這篇文章菌瘫,從3個(gè)宏觀的角度介紹了調(diào)度器蜗顽,也許你依然不知道調(diào)度器的原理,心里感覺(jué)模模糊糊雨让,沒(méi)關(guān)系雇盖,一步一步走,通過(guò)這篇文章希望你了解了:

  1. Go調(diào)度器和OS調(diào)度器的關(guān)系
  2. Go調(diào)度器的生命周期/總體流程
  3. P的數(shù)量等于GOMAXPROCS
  4. M需要通過(guò)綁定的P獲取G栖忠,然后執(zhí)行G崔挖,不斷重復(fù)這個(gè)過(guò)程

示例代碼

本文所有示例代碼都在Github,可通過(guò)閱讀原文訪問(wèn):golang_step_by_step/tree/master/scheduler

參考資料

最近的感受是:自己懂是一個(gè)層次庵寞,能寫出來(lái)需要抬升一個(gè)層次狸相,給他人講懂又需要抬升一個(gè)層次。希望朋友們有所收獲捐川。

  1. 如果這篇文章對(duì)你有幫助脓鹃,請(qǐng)點(diǎn)個(gè)贊/喜歡,感謝属拾。
  2. 本文作者:大彬
  3. 如果喜歡本文将谊,隨意轉(zhuǎn)載冷溶,但請(qǐng)保留此原文鏈接:http://lessisbetter.site/2019/03/26/golang-scheduler-2-macro-view/
image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市尊浓,隨后出現(xiàn)的幾起案子逞频,更是在濱河造成了極大的恐慌,老刑警劉巖栋齿,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苗胀,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡瓦堵,警方通過(guò)查閱死者的電腦和手機(jī)基协,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)菇用,“玉大人澜驮,你說(shuō)我怎么就攤上這事⊥锱福” “怎么了杂穷?”我有些...
    開(kāi)封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)卦绣。 經(jīng)常有香客問(wèn)我耐量,道長(zhǎng),這世上最難降的妖魔是什么滤港? 我笑而不...
    開(kāi)封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任廊蜒,我火速辦了婚禮,結(jié)果婚禮上溅漾,老公的妹妹穿的比我還像新娘山叮。我一直安慰自己,他們只是感情好添履,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布聘芜。 她就那樣靜靜地躺著,像睡著了一般缝龄。 火紅的嫁衣襯著肌膚如雪汰现。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天叔壤,我揣著相機(jī)與錄音瞎饲,去河邊找鬼。 笑死炼绘,一個(gè)胖子當(dāng)著我的面吹牛嗅战,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼驮捍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼疟呐!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起东且,我...
    開(kāi)封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤启具,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后珊泳,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鲁冯,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年色查,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了薯演。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡秧了,死狀恐怖跨扮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情验毡,我是刑警寧澤好港,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站米罚,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏丈探。R本人自食惡果不足惜录择,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望碗降。 院中可真熱鬧隘竭,春花似錦、人聲如沸讼渊。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)爪幻。三九已至菱皆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挨稿,已是汗流浹背仇轻。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奶甘,地道東北人篷店。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像臭家,于是被迫代替她去往敵國(guó)和親疲陕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子方淤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 介紹 上一篇文章我對(duì)操作系統(tǒng)級(jí)別的調(diào)度進(jìn)行了講解,這對(duì)理解 Go 語(yǔ)言的調(diào)度器是很重要的蹄殃。這篇文章携茂,我將解釋下 G...
    達(dá)菲格閱讀 8,006評(píng)論 1 30
  • [toc] 原文:Scheduling In Go : Part II - Go Scheduler 前言 這是本...
    豆腐匠閱讀 1,032評(píng)論 0 8
  • 前一篇文章大致介紹了Go語(yǔ)言調(diào)度的各個(gè)方面邑蒋,這篇文章通過(guò)介紹源碼來(lái)進(jìn)一步了解調(diào)度的一些過(guò)程。源碼是基于最新的Go ...
    EagleChan閱讀 3,697評(píng)論 2 5
  • 本篇文章是我對(duì) Go 語(yǔ)言并發(fā)性的理解總結(jié)按厘,適合初步了解并發(fā)医吊,對(duì) Go 語(yǔ)言的并發(fā)編程與調(diào)度器原理有興趣的讀者。 ...
    baiyi閱讀 1,238評(píng)論 0 4
  • 屁股當(dāng)然決定腦袋了逮京,傻瓜卿堂! 今天裝死裝了一天,因?yàn)樯×? 但是我就是這么的頑強(qiáng)懒棉,生病了也得繼續(xù)跑出來(lái)瞎說(shuō)幾句草描。 ...
    土豆醬的小酒館閱讀 463評(píng)論 0 1