說說Golang的runtime

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。runtime包含Go運行時的系統互動的操作,例如控制goruntine的功能。還有debug,pprof進行排查問題和運行時效能分析,tracer來抓取例外狀況事件資訊,如 goroutine的建立,加鎖解鎖狀態,系統調用進入推出和鎖定還有GC相關的事件,堆棧大小的改變以及進程的退出和開始事件等等;race進行競態關係檢查以及CGO的實現。總的來說運行時是調度器和GC,也是本文主要內容。


scheduler


首先說到調度,我們學習作業系統時知道,對於CPU時間片的調度,是系統的資源分派策略,如任務A在執行完後,選擇哪個任務來執行,使得某個因素(如進程總執行時間,或者磁碟尋道時間等)最小,達到最優的服務。這就是調度關注的問題。那麼Go的運行時的scheduler是什麼呢?我們為什麼需要它,因為我們知道OS核心已經有一個線程(進程)scheduler了嘛?

為什麼Go還要自己搞一套?想想我們是不是經常說Go牛逼啊,語言層級實現了並發,我們為什麼會這樣說呢?願意就在於此,Go有自己的scheduler。

說了這麼多,到底為什嗎?我們知道線程有自己的訊號掩碼,上下文環境以及各種控制資訊等,但這些很多特徵對於Go程式本身來說並不關心, 而且context環境切換的耗時費時費力費資源,更重要的是GC的原因,也是本文下部分說的,就是Go的記憶體回收需要stop the world,所有的goroutine停止,才能使得記憶體保持在一個一致的狀態。記憶體回收的時間會根據記憶體情況變化是不確定的,如果我們沒有自己的scheduler我們交給了OS自己的scheduler,我們就失去了控制,並且會有大量的線程需要停止工作。所以Go就需要自己單獨的開發一個自己使用的調度器,能夠自己管理goruntines,並且知道在什麼時候記憶體狀態是一致的,也就是說,對於OS而言運行時只需要為當時正在CPU核上啟動並執行那個線程等待即可,而不是等待所有的線程。
每一個Go程式都附帶一個runtime,runtime負責與底層作業系統互動,也都會有scheduler對goruntines進行調度。在scheduler中有三個非常重要的概念:P,M,G。

查看源碼/src/runtime/proc.go我們可以看到注釋:
// Goroutine scheduler// The scheduler's job is to distribute ready-to-run goroutines over worker threads.//// The main concepts are:// G - goroutine.// M - worker thread, or machine.// P - processor, a resource that is required to execute Go code.//     M must have an associated P to execute Go code, however it can be//     blocked or in a syscall w/o an associated P.//// Design doc at https://golang.org/s/go11sched.

我們也看下Go程式的啟動流程:
// The bootstrap sequence is:////call osinit//call schedinit//make & queue new G//call runtime·mstart//// The new G calls runtime·main.

想要明白詳細的流程可見:golang internals - Genius0101 - 部落格園
那麼scheduler究竟解決了什麼問題並如何管理goruntines呢?

想要自己解決調度,避不開一個問題那就是棧的管理,也就是說每個goroutine都有自己的棧,在建立goroutine時,就要同時建立對應的棧。那麼可知goroutine在執行時,棧空間會不停增長。 棧通常是連續增長的,每個進程中的各個線程共用虛擬記憶體空間,當有多個線程時,就需要為每個線程分配不同起始地址的棧,這就需要在分配棧之前先預估每個線程棧的大小。為瞭解決這個問題,就有了Split Stacks技術: 建立棧時,只分配一塊比較小的記憶體,如果進行某次函數調用導致棧空間不足時,就會在其他地方分配一塊新的棧空間。 新的空間不需要和老的棧空間連續。函數調用的參數會拷貝到新的棧空間中,接下來的函數執行都在新棧空間中進行。runtime的棧管理方式與此類似,但是為了更高的效率,使用了連續棧 (Golang連續棧) 實現方式也是先分配一塊固定大小的棧,在棧空間不足時,分配一塊更大的棧,並把舊的棧 全部拷貝到新棧中,這樣避免了Split Stacks方法可能導致的頻繁記憶體配置和釋放。

既然要調度那麼肯定要有自己的調度策略了,go使用搶佔式調度,goroutine的執行是可以被搶佔的。如果一個goroutine一直佔用CPU,長時間沒有被調度過, 就會被runtime搶佔掉,把CPU時間交給其他goroutine。詳見:Go Preemptive Scheduler Design Doc runtime在程式啟動時,會自動建立一個系統線程,運行sysmon()函數, sysmon()函數在整個程式生命週期中一直執行,負責監視各個Goroutine的狀態、判斷是否要進行記憶體回收等,sysmon()會調用retake()函數,retake()函數會遍曆所有的P,如果一個P處於執行狀態, 且已經連續執行了較長時間,就會被搶佔。

然後retake()調用preemptone()將P的stackguard0設為stackPreempt,這將導致該P中正在執行的G進行下一次函數調用時, 導致棧空間檢查失敗,進而觸發morestack(),在goschedImpl()函數中,會通過調用dropg()將G與M解除綁定;再調用globrunqput()將G加入全域runnable隊列中;最後調用schedule() 來用為當前P設定新的可執行檔G。

如:go function 即可啟動一個goroutine,所以每go出去一個語句被執行,runqueue隊列就在其末尾加入一個goroutine,並在下一個調度點,就從runqueue中取出,一個goroutine執行。同時每個P可以轉而投奔另一個OS線程,保證有足夠的線程來運行所以的context P,也就是說goruntine可以在合適時機在多個OS線程間切換,也可以一直在一個線程,這由調度器決定。

GC


GC一直是GoTeam Dev一直在最佳化的地方,它的效能也越來越好:
GC 1.5 vs 1.6
GC1.7
可能從圖上我們不好看出變化:在 Go 1.4 版本的時候它的 GC 在 300 毫秒的時候,但是在 1.5 版本 GC 已經最佳化得非常好了,壓縮到了40 毫秒。從 1.6 版本的 15 到 20 毫秒升級到 1.63 版本的 5 毫秒。又從 1.6.3 升級到 1. 7 版本的 3 毫秒以內,同樣在剛發布的1.8版本中,GC在低延遲方面的最佳化又給了我們大的驚喜,由於消除了GC的“stop-the-world stack re-scanning”,使得GC STW(stop-the-world)的時間通常低於100微秒,甚至經常低於10微秒,現在 GC 已經不是他們的問題了。GC 降下來了,CPU 使用率就上去了,1.7.3 和 1.8 版本中,CPU 會多利用一些,CPU 的使用率相對上升了一點,但是 GC 有很大的提升,當然這或多或少是以犧牲“吞吐”作為代價的,因此在Go 1.9中,GC的改進將持續進行,會在吞吐和低延遲上做一個很好的平衡。應該說,在 1.8 版本發布之後,1.9 版本現在引入了一個理念——goroutine 層級的GC,所以 1.9 版本可能還有更大的提升。

GC最佳化之路:
  1. 1.3 以前,使用的是比較蠢的傳統 Mark-Sweep 演算法。
  2. 1.3 版本進行了一下改進,把 Sweep 改為了並行操作。
  3. 1.5 版本進行了較大改進,使用了改進三色標記演算法,叫做“非分代的、非移動的、並發的、三色的標記清除垃圾收集器”,go 除了標準的三色收集以外,還有一個輔助回收功能,防止垃圾產生過快。分為兩個主要階段-markl階段:GC對對象和不再使用的記憶體進行標記;sweep階段,準備進行回收。這中間還分為兩個子階段,第一階段,暫停應用,結束上一次sweep,接著進入並發mark階段:找到正在使用的記憶體;第二階段,mark結束階段,這期間應用再一次暫停。最後,未使用的記憶體會被逐步回收,這個階段是非同步,不會STW。
  4. 1.6中,finalizer的掃描被移到了並發階段中,對於大量連線應用程式來說,GC的效能得到了顯著提升。
  5. 1.7號稱史上改進最多的版本,在GC上的改進也很顯著:並發的進行棧收縮,這樣我們既實現了低延遲,又避免了對runtime進行調優,只要使用標準的runtime就可以。
  6. 1.8 消除了GC的“stop-the-world stack re-scanning”

Go的GC目前來說已經做的非常好了,未來在1.9將更多在GC最佳化下對於吞吐和效率的平衡,我們一起期待!


參考文獻:

為Go語言GC正名-20秒到100微妙的演變史
Golang中goroutine的調度器詳解
https://www.zhihu.com/question/20862617

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.