Go語言如何實現stop the world?

Go語言如何實現stop the world?

本文基於 Go 1.13。

在某些垃圾回收器算法中,“停止世界”(Stop the World: STW,下同)是跟蹤內存使用最重要的階段,它會停止程序的執行,以掃描內存使用,並添加寫障礙。讓我們回顧一下它在內部如何工作,以及它可能面臨的潛在問題。

停止世界(Stop the world)

停止程序意味著停止所有正在運行的 goroutine。下面是一個執行 STW 的簡單程序:

<code>func main() {   runtime.GC()}/<code>

運行垃圾回收器,將觸發 STW 兩個階段。

有關垃圾回收器週期的更多信息,建議閱讀我的另外一篇文章 “Go:垃圾收集器如何標記內存? ① ”

第一步:搶佔所有正在運行的 goroutine:

Go語言如何實現stop the world?

goroutine 搶佔

一旦 goroutine 被搶佔,它們將在安全點停止。同時,P 處理器將(正在運行的代碼或在空閒列表)被標記為已停止,以不運行任何代碼:

Go語言如何實現stop the world?

P 標記為已停止

然後,Go 調度程序將運行,將每個 M 與其 P 各自分離,並將其放入空閒列表中:

Go語言如何實現stop the world?

M 已移至閒置清單

關於在每個上運行的 goroutine M,它們將在全局隊列中等待:

Go語言如何實現stop the world?

Goroutine 在全局隊列中等待

然後,一旦世界停止了,只有唯一活動的 goroutine 才能安全地運行,並在工作完成後啟動整個世界。下面跟蹤圖將有助於理解此階段發生在何時:

Go語言如何實現stop the world?

跟蹤 “ STW”階段

系統調用

“STW”階段也可能會影響系統調用,因為它們可能會在 STW 時返回。讓我們以一個密集執行系統調用的例子,並查看其如何處理:

<code>func main() {   var wg sync.WaitGroup   wg.Add(10)   for i := 0; i < 10; i++ {      go func() {         http.Get(`https://httpstat.us/200`)         wg.Done()      }()   }   wg.Wait()}/<code>

這是跟蹤:

Go語言如何實現stop the world?

STW 階段,系統調用正在結束。但是,由於沒有可用 P(如上一節所述,它們都被標記為已停止),goroutine 將被放入全局隊列,並在世界恢復時稍後運行。

延遲時間

“STW” 第三步涉及將所有 M 與其 P 分離。但是,Go 將等待它們自行停止:在調度程序運行時,在 syscall 調用中等。等待 goroutine 被搶佔應該很快,但是在某些情況下,可能會導致某些延遲。讓我們以一個極端的情況為例:

<code>func main() {   var t int   for i := 0;i < 20 ;i++  {      go func() {         for i := 0;i < 1000000000 ;i++ {            t++         }      }()   }   runtime.GC()}/<code>

在這裡,“ Stop the World”階段需要 2.6 秒:

Go語言如何實現stop the world?

沒有函數調用的 goroutine 將不會被搶佔,並且 P 在任務結束之前不會被釋放。這將迫使“STW”等待。有幾種解決方案可改善循環中的搶佔,有關此方面的更多信息,建議閱讀我另外一篇文章“ Go:Goroutine和搶佔 ② ”。


分享到:


相關文章: