golang的MPG調(diào)度模型是保障Go語(yǔ)言效率高的一個(gè)重要特性,本文詳細(xì)介紹了Go語(yǔ)言調(diào)度模型的設(shè)計(jì)。
前言
Please remember that at the end of the day, all programs that work on UNIX machines end up using C system calls to communicate with the UNIX kernel and perform most of their tasks. 所有在 UNIX 系統(tǒng)上運(yùn)行的程序最終都會(huì)通過(guò) C 系統(tǒng)調(diào)用來(lái)和內(nèi)核打交道。
用其他語(yǔ)言編寫(xiě)程序進(jìn)行系統(tǒng)調(diào)用,方法不外乎兩個(gè):一是自己封裝,二是依賴 glibc、或者其他的運(yùn)行庫(kù)。Go 語(yǔ)言選擇了前者,把系統(tǒng)調(diào)用都封裝到了 syscall 包。封裝時(shí)也同樣得通過(guò)匯編實(shí)現(xiàn)。
異步系統(tǒng)調(diào)用 G 會(huì)和MP分離(G掛到netpoller),同步系統(tǒng)調(diào)用 GM 會(huì)和P分離(P另尋M),生動(dòng)的說(shuō)明了GPM相對(duì)GM的精妙之處。
阻塞
在 Go 里面阻塞主要分為以下 4 種場(chǎng)景:
由于原子、互斥量或通道操作調(diào)用導(dǎo)致 Goroutine 阻塞,調(diào)度器將把當(dāng)前阻塞的 Goroutine 切換出去,重新調(diào)度 LRQ 上的其他 Goroutine;
由于網(wǎng)絡(luò)請(qǐng)求和 IO 操作導(dǎo)致 Goroutine 阻塞。Go 程序提供了網(wǎng)絡(luò)輪詢器(NetPoller)來(lái)處理網(wǎng)絡(luò)請(qǐng)求和 IO 操作的問(wèn)題,其后臺(tái)通過(guò) kqueue(MacOS),epoll(Linux)或 iocp(Windows)來(lái)實(shí)現(xiàn) IO 多路復(fù)用。通過(guò)使用 NetPoller 進(jìn)行網(wǎng)絡(luò)系統(tǒng)調(diào)用,調(diào)度器可以防止 Goroutine 在進(jìn)行這些系統(tǒng)調(diào)用時(shí)阻塞 M。
這可以讓 M 執(zhí)行 P 的 LRQ 中其他的 Goroutines,而不需要?jiǎng)?chuàng)建新的 M。執(zhí)行網(wǎng)絡(luò)系統(tǒng)調(diào)用不需要額外的 M,網(wǎng)絡(luò)輪詢器使用系統(tǒng)線程,它時(shí)刻處理一個(gè)有效的事件循環(huán),有助于減少操作系統(tǒng)上的調(diào)度負(fù)載。
用戶層眼中看到的 Goroutine 中的“block socket”,實(shí)現(xiàn)了 goroutine-per-connection 簡(jiǎn)單的網(wǎng)絡(luò)編程模式。實(shí)際上是通過(guò) Go runtime 中的 netpoller 通過(guò) Non-block socket + I/O 多路復(fù)用機(jī)制“模擬”出來(lái)的。
當(dāng)調(diào)用一些系統(tǒng)方法的時(shí)候(如文件 I/O),如果系統(tǒng)方法調(diào)用的時(shí)候發(fā)生阻塞,這種情況下,網(wǎng)絡(luò)輪詢器(NetPoller)無(wú)法使用,而進(jìn)行系統(tǒng)調(diào)用的 G1 將阻塞當(dāng)前 M1。調(diào)度器引入 其它M 來(lái)服務(wù) M1 的P。
如果在 Goroutine 去執(zhí)行一個(gè) sleep 操作,導(dǎo)致 M 被阻塞了。Go 程序后臺(tái)有一個(gè)監(jiān)控線程 sysmon,它監(jiān)控那些長(zhǎng)時(shí)間運(yùn)行的 G 任務(wù)然后設(shè)置可以強(qiáng)占的標(biāo)識(shí)符,別的 Goroutine 就可以搶先進(jìn)來(lái)執(zhí)行。
系統(tǒng)調(diào)用
Go 語(yǔ)言通過(guò) Syscall 和 Rawsyscall 等使用匯編語(yǔ)言編寫(xiě)的方法封裝了操作系統(tǒng)提供的所有系統(tǒng)調(diào)用,其中 Syscall 在 Linux 386 上的實(shí)現(xiàn)如下:
TEXT ·Syscall(SB),NOSPLIT,$0-28
CALL runtime·entersyscall(SB)
MOVL trap+0(FP), AX // syscall entry
MOVL a1+4(FP), BX
MOVL a2+8(FP), CX
MOVL a3+12(FP), DX
MOVL $0, SI
MOVL $0, DI
INVOKE_SYSCALL
CMPL AX, $0xfffff001
JLS ok
MOVL $-1, r1+16(FP)
MOVL $0, r2+20(FP)
NEGL AX
MOVL AX, err+24(FP)
CALL runtime·exitsyscall(SB)
RET
ok:
MOVL AX, r1+16(FP)
MOVL DX, r2+20(FP)
MOVL $0, err+24(FP)
CALL runtime·exitsyscall(SB)
RET
Golang - 調(diào)度剖析 https://segmentfault.com/a/1190000016611742
Go: Goroutine, OS Thread and CPU Management https://medium.com/a-journey-with-go/go-goroutine-os-thread-and-cpu-management-2f5a5eaf518a
Go optimizes the system calls — whatever it is blocking or not — by wrapping them up in the runtime. This wrapper will automatically dissociate the P from the thread M and allow another thread to run on it.
異步系統(tǒng)調(diào)用
通過(guò)使用網(wǎng)絡(luò)輪詢器進(jìn)行網(wǎng)絡(luò)系統(tǒng)調(diào)用,調(diào)度器可以防止 Goroutine 在進(jìn)行這些系統(tǒng)調(diào)用時(shí)阻塞M。這可以讓M執(zhí)行P的 LRQ 中其他的 Goroutines,而不需要?jiǎng)?chuàng)建新的M。有助于減少操作系統(tǒng)上的調(diào)度負(fù)載。
G1正在M上執(zhí)行,還有 3 個(gè) Goroutine 在 LRQ 上等待執(zhí)行
接下來(lái),G1想要進(jìn)行網(wǎng)絡(luò)系統(tǒng)調(diào)用,因此它被移動(dòng)到網(wǎng)絡(luò)輪詢器并且處理異步網(wǎng)絡(luò)系統(tǒng)調(diào)用。然后,M可以從 LRQ 執(zhí)行另外的 Goroutine。
最后:異步網(wǎng)絡(luò)系統(tǒng)調(diào)用由網(wǎng)絡(luò)輪詢器完成,G1被移回到P的 LRQ 中。一旦G1可以在M上進(jìn)行上下文切換,它負(fù)責(zé)的 Go 相關(guān)代碼就可以再次執(zhí)行。
同步系統(tǒng)調(diào)用
G1將進(jìn)行同步系統(tǒng)調(diào)用以阻塞M1
調(diào)度器介入后:識(shí)別出G1已導(dǎo)致M1阻塞,此時(shí),調(diào)度器將M1與P分離,同時(shí)也將G1帶走。然后調(diào)度器引入新的M2來(lái)服務(wù)P。
阻塞的系統(tǒng)調(diào)用完成后:G1可以移回 LRQ 并再次由P執(zhí)行。如果這種情況需要再次發(fā)生,M1將被放在旁邊以備將來(lái)使用。
sysmon 協(xié)程
在 linux 內(nèi)核中有一些執(zhí)行定時(shí)任務(wù)的線程, 比如定時(shí)寫(xiě)回臟頁(yè)的 pdflush, 定期回收內(nèi)存的 kswapd0, 以及每個(gè) cpu 上都有一個(gè)負(fù)責(zé)負(fù)載均衡的 migration 線程等。在 go 運(yùn)行時(shí)中也有類似的協(xié)程 sysmon. sysmon 運(yùn)行在 M,且不需要 P。它會(huì)每隔一段時(shí)間檢查 Go 語(yǔ)言runtime,確保程序沒(méi)有進(jìn)入異常狀態(tài)。
系統(tǒng)監(jiān)控的觸發(fā)時(shí)間就會(huì)穩(wěn)定在 10ms,功能比較多:
檢查死鎖runtime.checkdead
運(yùn)行計(jì)時(shí)器 — 獲取下一個(gè)需要被觸發(fā)的計(jì)時(shí)器;
定時(shí)從 netpoll 中獲取 ready 的協(xié)程
Go 的搶占式調(diào)度
當(dāng) sysmon 發(fā)現(xiàn) M 已運(yùn)行同一個(gè) G(Goroutine)10ms 以上時(shí),它會(huì)將該 G 的內(nèi)部參數(shù) preempt 設(shè)置為 true。然后,在函數(shù)序言中,當(dāng) G 進(jìn)行函數(shù)調(diào)用時(shí),G 會(huì)檢查自己的 preempt 標(biāo)志,如果它為 true,則它將自己與 M 分離并推入“全局隊(duì)列”。由于它的工作方式(函數(shù)調(diào)用觸發(fā)),在 for{} 的情況下并不會(huì)發(fā)生搶占,如果沒(méi)有函數(shù)調(diào)用,即使設(shè)置了搶占標(biāo)志,也不會(huì)進(jìn)行該標(biāo)志的檢查。
Go1.14 引入搶占式調(diào)度(使用信號(hào)的異步搶占機(jī)制),sysmon 仍然會(huì)檢測(cè)到運(yùn)行了 10ms 以上的 G(goroutine)。然后,sysmon 向運(yùn)行 G 的 P 發(fā)送信號(hào)(SIGURG)。Go 的信號(hào)處理程序會(huì)調(diào)用P上的一個(gè)叫作 gsignal 的 goroutine 來(lái)處理該信號(hào),將其映射到 M 而不是 G,并使其檢查該信號(hào)。gsignal 看到搶占信號(hào),停止正在運(yùn)行的 G。
在滿足條件時(shí)觸發(fā)垃圾收集回收內(nèi)存;
打印調(diào)度信息,歸還內(nèi)存等定時(shí)任務(wù)。
轉(zhuǎn)自:bert.li@ximalaya.com
qiankunli.github.io/2020/11/21/goroutine_system_call.html
編輯:jq
-
go語(yǔ)言
+關(guān)注
關(guān)注
1文章
159瀏覽量
9370
原文標(biāo)題:Golang 系統(tǒng)調(diào)用與阻塞處理
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
鴻蒙中Stage模型與FA模型詳解
如何借助大語(yǔ)言模型打造人工智能生態(tài)系統(tǒng)

語(yǔ)言模型管理的作用
AI大語(yǔ)言模型開(kāi)發(fā)步驟
大語(yǔ)言模型開(kāi)發(fā)框架是什么
大語(yǔ)言模型開(kāi)發(fā)語(yǔ)言是什么
云端語(yǔ)言模型開(kāi)發(fā)方法
在學(xué)習(xí)go語(yǔ)言的過(guò)程踩過(guò)的坑
大語(yǔ)言模型如何開(kāi)發(fā)
go語(yǔ)言如何解決并發(fā)問(wèn)題

三十分鐘入門(mén)基礎(chǔ)Go Java小子版

評(píng)論