圖解 Go 語言 time-Sleep 的實現原理
我們寫程序的時候,一定遇到過需要讓程序休眠一段時間再執行的場景,這個時候我們一般會想到用 Sleep
方法,Java 語言有Thread.Sleep
, PHP 應該是有一個sleep
函數,同樣的 Go 語言有內置的 time.Sleep
方法。
這篇文章我們來簡單梳理一下,Go 語言的time.Sleep
是怎麼實現程序的休眠和喚醒的。
在 Go 的程序中,使用time.Sleep(d duration)
方法時,它會阻塞所在 Goroutine 的執行直到 d 時間結束。
它的實現原理肯定是跟 Go 語言的 GMP 模型有關,當執行到time.Sleep
時 Go 調度器讓 G 與協同線程 M 解綁,等時間到了之後再讓 G 與 M 進行綁定繼續執行後面的任務,可是面試的時候如果只回答道這個程度,感覺還是會被髮好人牌 -- 回家等消息吧。
接下來我們把整個過程詳細梳理一下,首先來複習一下 Go 語言調度器的 G-M-P 模型
-
G 表示一個 Goroutine,對於 Go 調度器來說,每個 G 都是一個待執行的任務。
-
P 表示 Go 調度器中的處理器,它是系統線程和 Goroutine 的中間層
-
M 表示的是操作系統的線程,由操作系統進行調度和管理
調度器中的處理器 P 會負責把等待隊列中的 Goroutine 調度到 M 上去執行,它也能在 Goroutine 進行一些諸如 I/O 這樣的操作時讓 Goroutine 及時讓出線程資源。
通過處理器 P 的調度,每一個線程都能夠執行多個 Goroutine,提高線程的利用率。G 的調度過程可以用下面這張圖表示:
-
圖中的
runq
是 P 持有的等待執行的隊列 -
G 被調度到 M 上後,M 的 curg 屬性表示當前正在執行的 G。
-
g0 是 M 上持有調度棧的 特殊的 Goroutine,它會深度參與運行時的調度過程。
-
我們假設 GT 爲代碼中有
time.Sleep
的 G, 它前面的 G1 已經被調度到了 M 上去執行。
GT 前面的 G1 執行完成與 M 解綁後,調度器 P 繼續進行調度,此時 GT 處於等待隊列的隊頭,所以它被調度到 M 上去執行。goready
方法。
定時器在 P 中的結構爲一個四叉堆,最早到時的定時器的放在堆頂上,這個數據結構我沒研究過,懂的老哥可以在評論區裏補充。
gopark
方法完成的,它釋放當前 Goroutine 與 M 的連接後,該 Goroutine 脫離當前的 M 被掛起,進入Gwaiting
狀態
此時 GT 不在運行隊列上,調度器會調度一個新的 Goroutine G3 到 M 上執行。
喚起 GT 是通過執行創建定時器時指定的 goready
方法, 把 GT 插入到 P 的等待隊列的頭部。
回到 P 執行隊列頭部的 GT 會被馬上調度到 M 上去執行。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/02w-k5YgYxMC_gxbRdNpJQ