Go?runtime?調(diào)度器之系統(tǒng)調(diào)用引起的搶占
0. 前言
第八講介紹了當(dāng) goroutine 運(yùn)行時(shí)間過(guò)長(zhǎng)會(huì)被搶占的情況。這一講繼續(xù)看 goroutine 執(zhí)行系統(tǒng)調(diào)用時(shí)間過(guò)長(zhǎng)的搶占。
1. 系統(tǒng)調(diào)用時(shí)間過(guò)長(zhǎng)的搶占
看下面的示例:
func longSyscall() { timeout := syscall.NsecToTimeval(int64(5 * time.Second)) fds := make([]syscall.FdSet, 1) if _, err := syscall.Select(0, &fds[0], nil, nil, &timeout); err != nil { fmt.Println("Error:", err) } fmt.Println("Select returned after timeout") } func main() { threads := runtime.GOMAXPROCS(0) for i := 0; i < threads; i++ { go longSyscall() } time.Sleep(8 * time.Second) }
longSyscall goroutine 執(zhí)行一個(gè) 5s 的系統(tǒng)調(diào)用,在系統(tǒng)調(diào)用過(guò)程中,sysmon 會(huì)監(jiān)控 longSyscall,發(fā)現(xiàn)執(zhí)行系統(tǒng)調(diào)用過(guò)長(zhǎng),會(huì)對(duì)其搶占。
回到 sysmon
線(xiàn)程看它是怎么搶占系統(tǒng)調(diào)用時(shí)間過(guò)長(zhǎng)的 goroutine 的。
func sysmon() { ... idle := 0 // how many cycles in succession we had not wokeup somebody delay := uint32(0) ... for { if idle == 0 { // start with 20us sleep... delay = 20 } else if idle > 50 { // start doubling the sleep after 1ms... delay *= 2 } if delay > 10*1000 { // up to 10ms delay = 10 * 1000 } usleep(delay) ... // retake P's blocked in syscalls // and preempt long running G's if retake(now) != 0 { idle = 0 } else { idle++ } ... } }
類(lèi)似于運(yùn)行時(shí)間過(guò)長(zhǎng)的 goroutine,調(diào)用 retake
進(jìn)行搶占:
func retake(now int64) uint32 { n := 0 lock(&allpLock) for i := 0; i < len(allp); i++ { pp := allp[i] if pp == nil { continue } pd := &pp.sysmontick s := pp.status sysretake := false if s == _Prunning || s == _Psyscall { // goroutine 處于 _Prunning 或 _Psyscall 時(shí)會(huì)搶占 // Preempt G if it's running for too long. t := int64(pp.schedtick) if int64(pd.schedtick) != t { pd.schedtick = uint32(t) pd.schedwhen = now } else if pd.schedwhen+forcePreemptNS <= now { // 對(duì)于 _Prunning 或者 _Psyscall 運(yùn)行時(shí)間過(guò)長(zhǎng)的情況,都會(huì)進(jìn)入 preemptone // preemptone 我們?cè)谶\(yùn)行時(shí)間過(guò)長(zhǎng)的搶占中介紹過(guò),它主要設(shè)置了 goroutine 的標(biāo)志位 // 對(duì)于處于系統(tǒng)調(diào)用的 goroutine,這么設(shè)置并不會(huì)搶占。因?yàn)榫€(xiàn)程一直處于系統(tǒng)調(diào)用狀態(tài) preemptone(pp) // In case of syscall, preemptone() doesn't // work, because there is no M wired to P. sysretake = true } } if s == _Psyscall { // Retake P from syscall if it's there for more than 1 sysmon tick (at least 20us). // P 處于系統(tǒng)調(diào)用之中,需要檢查是否需要搶占 // syscalltick 用于記錄系統(tǒng)調(diào)用的次數(shù),在完成系統(tǒng)調(diào)用之后加 1 t := int64(pp.syscalltick) if !sysretake && int64(pd.syscalltick) != t { // pd.syscalltick != pp.syscalltick,說(shuō)明已經(jīng)不是上次觀(guān)察到的系統(tǒng)調(diào)用了, // 而是另外一次系統(tǒng)調(diào)用,需要重新記錄 tick 和 when 值 pd.syscalltick = uint32(t) pd.syscallwhen = now continue } // On the one hand we don't want to retake Ps if there is no other work to do, // but on the other hand we want to retake them eventually // because they can prevent the sysmon thread from deep sleep. // 如果滿(mǎn)足下面三個(gè)條件的一個(gè)則執(zhí)行搶占: // 1. 線(xiàn)程綁定的本地隊(duì)列中有可運(yùn)行的 goroutine // 2. 沒(méi)有無(wú)所事事的 P(表示大家都挺忙的,那就不要執(zhí)行系統(tǒng)調(diào)用那么長(zhǎng)時(shí)間占資源了) // 3. 執(zhí)行系統(tǒng)調(diào)用時(shí)間超過(guò) 10ms 的 if runqempty(pp) && sched.nmspinning.Load()+sched.npidle.Load() > 0 && pd.syscallwhen+10*1000*1000 > now { continue } // 下面是執(zhí)行搶占的邏輯 unlock(&allpLock) // Need to decrement number of idle locked M's // (pretending that one more is running) before the CAS. // Otherwise the M from which we retake can exit the syscall, // increment nmidle and report deadlock. incidlelocked(-1) if atomic.Cas(&pp.status, s, _Pidle) { // 將 P 的狀態(tài)更新為 _Pidle n++ // 搶占次數(shù) + 1 pp.syscalltick++ // 系統(tǒng)調(diào)用搶占次數(shù) + 1 handoffp(pp) // handoffp 搶占 } incidlelocked(1) lock(&allpLock) } } unlock(&allpLock) return uint32(n) }
進(jìn)入 handoffp
:
// Hands off P from syscall or locked M. // Always runs without a P, so write barriers are not allowed. // //go:nowritebarrierrec func handoffp(pp *p) { // if it has local work, start it straight away // 這里如果 P 的本地有工作(goroutine),或者全局有工作的話(huà) // 將 P 和其它線(xiàn)程綁定,其它線(xiàn)程指的是不是執(zhí)行系統(tǒng)調(diào)用的那個(gè)線(xiàn)程 // 執(zhí)行系統(tǒng)調(diào)用的線(xiàn)程不需要 P 了,這時(shí)候把 P 釋放出來(lái),算是資源的合理利用,相比于線(xiàn)程,P 是有限的 if !runqempty(pp) || sched.runqsize != 0 { startm(pp, false, false) return } ... // no local work, check that there are no spinning/idle M's, // otherwise our help is not required if sched.nmspinning.Load()+sched.npidle.Load() == 0 && sched.nmspinning.CompareAndSwap(0, 1) { // TODO: fast atomic sched.needspinning.Store(0) startm(pp, true, false) return } ... // 判斷全局隊(duì)列有沒(méi)有工作要處理 if sched.runqsize != 0 { unlock(&sched.lock) startm(pp, false, false) return } ... // 如果都沒(méi)有工作,那就把 P 放到全局空閑隊(duì)列中 pidleput(pp, 0) unlock(&sched.lock) }
可以看到搶占系統(tǒng)調(diào)用過(guò)長(zhǎng)的 goroutine,這里搶占的意思是釋放系統(tǒng)調(diào)用線(xiàn)程所綁定的 P,搶占的意思不是不讓線(xiàn)程做系統(tǒng)調(diào)用,而是把 P 釋放出來(lái)。(由于前面設(shè)置了這個(gè) goroutine 的 stackguard0,類(lèi)似于 運(yùn)行時(shí)間過(guò)長(zhǎng) goroutine 的搶占 的流程還是會(huì)走一遍的)。
我們看一個(gè)示意圖可以更直觀(guān)清晰的了解這個(gè)過(guò)程:
handoff
結(jié)束之后,增加搶占次數(shù) n,retake
返回:
func sysmon() { ... idle := 0 // how many cycles in succession we had not wokeup somebody delay := uint32(0) for { if idle == 0 { // start with 20us sleep... delay = 20 // 如果 idle == 0,表示 sysmon 需要打起精神來(lái),要隔 20us 監(jiān)控一次 } else if idle > 50 { // start doubling the sleep after 1ms... delay *= 2 // 如果 idle 大于 50,表示循環(huán)了 50 次都沒(méi)有搶占,sysmon 將加倍休眠,比較空,sysmon 也不浪費(fèi)資源,先睡一會(huì) } if delay > 10*1000 { // up to 10ms delay = 10 * 1000 // 當(dāng)然,不能無(wú)限制睡下去。最大休眠時(shí)間設(shè)置成 10ms } if retake(now) != 0 { idle = 0 // 有搶占,則 idle = 0,表示 sysmon 要忙起來(lái) } else { idle++ // 沒(méi)有搶占,idle + 1 } ... } ... }
2. 小結(jié)
本講介紹了系統(tǒng)調(diào)用時(shí)間過(guò)長(zhǎng)引起的搶占。下一講將繼續(xù)介紹異步搶占。
到此這篇關(guān)于Go runtime 調(diào)度器之系統(tǒng)調(diào)用引起的搶占的文章就介紹到這了,更多相關(guān)Go runtime 調(diào)度器 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang?slice中常見(jiàn)性能優(yōu)化手段總結(jié)
這篇文章主要為大家詳細(xì)一些Golang開(kāi)發(fā)中常用的slice關(guān)聯(lián)的性能優(yōu)化手段,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-10-10Golang操作DuckDB實(shí)戰(zhàn)案例分享
DuckDB是一個(gè)嵌入式SQL數(shù)據(jù)庫(kù)引擎,它與眾所周知的SQLite非常相似,但它是為olap風(fēng)格的工作負(fù)載設(shè)計(jì)的,DuckDB支持各種數(shù)據(jù)類(lèi)型和SQL特性,憑借其在以?xún)?nèi)存為中心的環(huán)境中處理高速分析的能力,它迅速受到數(shù)據(jù)科學(xué)家和分析師的歡迎,在這篇博文中,我們將探索在Go中使用DuckDB2025-01-01利用golang進(jìn)行OpenCV學(xué)習(xí)和開(kāi)發(fā)的步驟
目前,OpenCV逐步成為一個(gè)通用的基礎(chǔ)研究和產(chǎn)品開(kāi)發(fā)平臺(tái),下面這篇文章主要給大家介紹了關(guān)于利用golang進(jìn)行OpenCV學(xué)習(xí)和開(kāi)發(fā)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2018-09-09基于Go?goroutine實(shí)現(xiàn)一個(gè)簡(jiǎn)單的聊天服務(wù)
對(duì)于聊天服務(wù),想必大家都不會(huì)陌生,因?yàn)樵谖覀兊纳钪薪?jīng)常會(huì)用到,本文我們用?Go?并發(fā)來(lái)實(shí)現(xiàn)一個(gè)聊天服務(wù)器,這個(gè)程序可以讓一些用戶(hù)通過(guò)服務(wù)器向其它所有用戶(hù)廣播文本消息,文中通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06淺析Go語(yǔ)言中的方法集合與選擇receiver類(lèi)型
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中的方法集合與選擇receiver類(lèi)型的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),對(duì)我們深入學(xué)習(xí)go語(yǔ)言有一定的幫助,需要的可以參考下2023-11-11Go中調(diào)用JS代碼(otto)的實(shí)現(xiàn)示例
Otto是一個(gè)用Go語(yǔ)言實(shí)現(xiàn)的JavaScript解釋器,可用于執(zhí)行和操作JavaScript代碼,適合在Go項(xiàng)目中執(zhí)行簡(jiǎn)單的JS腳本,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-10-10golang 如何用反射reflect操作結(jié)構(gòu)體
這篇文章主要介紹了golang 用反射reflect操作結(jié)構(gòu)體的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04golang在GRPC中設(shè)置client的超時(shí)時(shí)間
這篇文章主要介紹了golang在GRPC中設(shè)置client的超時(shí)時(shí)間,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04Golang并發(fā)繞不開(kāi)的重要組件之Goroutine詳解
Goroutine、Channel、Context、Sync都是Golang并發(fā)編程中的幾個(gè)重要組件,這篇文中主要為大家介紹了Goroutine的相關(guān)知識(shí),需要的可以參考一下2023-06-06