圖解 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 模型

調度器中的處理器 P 會負責把等待隊列中的 Goroutine 調度到 M 上去執行,它也能在 Goroutine 進行一些諸如 I/O 這樣的操作時讓 Goroutine 及時讓出線程資源。

通過處理器 P 的調度,每一個線程都能夠執行多個 Goroutine,提高線程的利用率。G 的調度過程可以用下面這張圖表示:

GT 前面的 G1 執行完成與 M 解綁後,調度器 P 繼續進行調度,此時 GT 處於等待隊列的隊頭,所以它被調度到 M 上去執行。當執行到了代碼中的 time.Sleep 時,GT 會與 M 解綁,同時用 GT 的 Sleep 方法中指定的時間加上其他一些信息會生成 Timer 記錄到 P 上,Timer 中會包含喚起 GT 時要調用的 goready 方法。

定時器在 P 中的結構爲一個四叉堆,最早到時的定時器的放在堆頂上,這個數據結構我沒研究過,懂的老哥可以在評論區裏補充。

GT 與 M 的解綁是通過調用gopark方法完成的,它釋放當前 Goroutine 與 M 的連接後,該 Goroutine 脫離當前的 M 被掛起,進入Gwaiting狀態

此時 GT 不在運行隊列上,調度器會調度一個新的 Goroutine G3 到 M 上執行。G3 執行完或者是也被掛起後,調度器會進行下一輪調度,這時假設 P 檢查自己記錄的定時器時發現 GT 的定時器到時間了,是時候把 GT 重新喚起來了。

喚起 GT 是通過執行創建定時器時指定的 goready 方法, 把 GT 插入到 P 的等待隊列的頭部。

回到 P 執行隊列頭部的 GT 會被馬上調度到 M 上去執行。

以上就是 Go 代碼中遇到 time.Sleep 後的整個調度過程,有興趣深入研究的,可以對照着這裏總結的步驟一步步地去看源碼。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/02w-k5YgYxMC_gxbRdNpJQ