您好,登錄后才能下訂單哦!
本篇文章為大家展示了Go語言中單核CPU開兩個Goroutine時其中一個死循環會怎么樣,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
今天的男主角,是與 Go 工程師有調度相關的知識,那就是 “單核 CPU,開兩個 Goroutine,其中一個死循環,會怎么樣?”
請在此處默念自己心目中的答案,再往和煎魚一起研討一波 Go 的技術哲學。
針對這個問題,我們需要把問題剖開來看看,其具有以下幾個元素:
根據這道題的題意,可大致理解其想要問的是 Go 調度相關的一些知識理解。
第一個要點,就是要明確 “計算機只有一個單核 CPU” 這一個變量定義,對 Go 程序會產生什么影響,否則很難繼續展開。
既然明確涉及 Goroutine,這里就會考察到你對 Go 的調度模型 GMP 的基本理解了。
從單核 CPU 來看,最大的影響就是 GMP 模型中的 P,因為 P 的數量默認是與 CPU 核數(GOMAXPROCS)保持一致的。
go func
就是生成了一個 G。GOMAXPROCS
進行修改。這三者交互實際來源于 Go 的 M: N 調度模型。也就是 M 必須與 P 進行綁定,然后不斷地在 M 上循環尋找可運行的 G 來執行相應的任務。
第二個要點,就是 Goroutine 的數量和運行模式都是受限的。有兩個 Goroutine,一個 Goroutine 在死循環,另外一個在正常運行。
這可以理解為 Main Goroutine + 起了一個新 Goroutine 跑著死循環,因為本身 main 函數就是一個主協程在運行著,沒毛病。
需要注意的是,Goroutine 里跑著死循環,也就是時時刻刻在運行著 “業務邏輯”。這塊需要與單核 CPU 關聯起來,考慮是否會一直阻塞住,把整個 Go 進程運行給 hang 住了?
注:但若是在現場面試,可以先枚舉出這種場景,詮釋清楚后。再補充提問面試官,是否這類場景?
第三個要點,是一個隱性的拓展點。如果你是一個老 Go 粉,經常關注 Go 版本的更新情況(至少大版本),則應該會知道 Go 的調度是會變動的(會在后面的小節講解)。
因此本文這個問題,在不同的 Go 語言版本中,結果可能會是不一樣的。但是面試官并沒有指出,這里就需要考慮到:
如果你注意到了,是一個小亮點,說明你在這塊有一定的知識積累。
在剛剛過去的 3s 中,你已經把上面的考量都在大腦中過了一遍。接下來我們正式進入實戰演練,構造一個例子:
// Main Goroutine
func main() {
// 模擬單核 CPU
runtime.GOMAXPROCS(1)
// 模擬 Goroutine 死循環
go func() {
for {
}
}()
time.Sleep(time.Millisecond)
fmt.Println("腦子進煎魚了")
}
在上面的例子中,我們通過以下方式達到了面試題所需的目的:
runtime.GOMAXPROCS
方法模擬了單核 CPU 下只有一個 P 的場景。思考一下:這段程序是否會輸出 ”腦子進煎魚了“ 呢,為什么?
答案是:
這是怎么回事呢,這兩種情況分別對應了什么原因和標準,Go 版本的變更有帶來了什么影響?
顯然,這段程序是有一個 Goroutine 是正在執行死循環,也就是說他肯定無法被搶占。
這段程序中更沒有涉及主動放棄執行權的調用(runtime.Gosched),又或是其他調用(可能會導致執行權轉移)的行為。因此這個 Goroutine 是沒機會溜號的,只能一直打工...
那為什么主協程(Main Goroutine)會無法運行呢,其實原因是會優先調用休眠,但由于單核 CPU,其只有唯一的 P。唯一的 P 又一直在打工不愿意下班(執行 for 死循環,被迫無限加班)。
因此主協程永遠沒有機會唄調度,所以這個 Go 程序自然也就一直阻塞在了執行死循環的 Goroutine 中,永遠無法下班(執行完畢,退出程序)。
那為什么 Go1.14 及以后的版本,又能正常輸出了呢?
主要還是在 Go1.14 實現了基于信號的搶占式調度,以此來解決上述一些仍然無法被搶占解決的場景。
主要原理是Go 程序在啟動時,會在 runtime.sighandler
方法注冊并且綁定 SIGURG
信號:
func mstartm0() {
...
initsig(false)
}
func initsig(preinit bool) {
for i := uint32(0); i < _NSIG; i++ {
...
setsig(i, funcPC(sighandler))
}
}
綁定相應的 runtime.doSigPreempt
搶占方法:
func sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g) {
...
if sig == sigPreempt && debug.asyncpreemptoff == 0 {
// 執行搶占
doSigPreempt(gp, c)
}
}
同時在調度的 runtime.sysmon
方法會調用 retake
方法處理一下兩種場景:
該方法會檢測符合場景的 P,當滿足上述兩個場景之一時,就會發送信號給 M。M 收到信號后將會休眠正在阻塞的 Goroutine,調用綁定的信號方法,并進行重新調度。以此來解決這個問題。
注:在 Go 語言中,sysmon 會用于檢測搶占。sysmon 是 Go 的 Runtime 的系統檢測器,sysmon 可進行 forcegc、netpoll、retake 等一系列騷操作(via @xiaorui)。
在這篇文章中,我們針對 ”單核 CPU,開兩個 Goroutine,其中一個死循環,會怎么樣?“ 這個問題進行了展開剖析。
針對不同 Go 語言版本,不同程序邏輯的表現形式都不同,但背后的基本原理都是與 Go 調度模型和搶占有關。
上述內容就是Go語言中單核CPU開兩個Goroutine時其中一個死循環會怎么樣,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。