剖析使Go語言高效的5個特性(5/5): Goroutine的棧管理

翻譯原文連接 轉帖/轉載請註明出處程序員

英文原文連接 發表於2014/06/07緩存

Goroutine的棧管理

在上一篇文章裏,咱們已經討論了goroutine減小了對上百個併發運行的線程的管理開銷。這裏咱們要在討論下goroutine的另一個方面,它的棧管理。併發

下面是一個進程的內存佈局圖。這個圖裏咱們關心的是堆和棧的位置。函數

圖片描述

一般在進程的尋址空間裏,堆是在內存的底部,從程序的可執行指令存儲空間(text)開始向上衍生。棧則位於虛擬地址空間的頂部,並向下衍生。佈局

若是堆和棧衍生後相互覆蓋的話,那結果是災難性的。操做系統一般會在堆和棧之間設置一塊不可寫的內存區域。若是堆和棧衍生到一塊兒的話,程序就會退出。這塊內存區域叫作保護頁(guard page)。它限制了進程的棧的大小。這個限制一般在幾M的量級。post

圖片描述

咱們前面講到過線程是共享尋址空間的。可是每一個線程,它又必需要有它本身的棧。性能

圖片描述

由於很難預測一個線程須要多大的棧空間,不少的內存空間都被預留給了線程的棧和保護頁以指望預留的棧空間足夠大,這樣保護頁不會被觸及到。這樣作的缺點就是隨着程序裏線程數的增長,可用的尋址空間也相應地減小了。學習

咱們已經討論過Go的運行環境會把大量的goroutine在少數的線程上調度。那麼這些goroutine的棧是怎麼管理的呢?spa

圖片描述

Go語言沒有使用保護頁機制。Go的編譯器會在每一個函數調用的時候插入一段代碼來檢查是否有足夠的棧空間來運行被調用的函數。若是空間不足,Go的運行環境就會分配更多的棧空間。由於有了這個檢查機制,一個goroutine的初始棧能夠很小。這樣Go程序員就能夠把goroutine做爲相對廉價的資源來使用。操作系統

下圖顯示了Go 1.2裏是如何管理棧的。

圖片描述

當G調用H的時候,沒有足夠的棧空間來讓H運行,這時候Go運行環境就會從堆裏分配一個新的棧內存塊去讓H運行。在H返回到G以前,新分配的內存塊被釋放回堆。這種管理棧的方法通常都工做得很好。但對有些代碼,特別是遞歸調用,它會形成程序不停地分配和釋放新的內存空間。舉個例子,在一個程序裏,函數G會在一個循環裏調用不少次H函數。每次調用都會分配一塊新的內存空間。這就是熱分裂問題(hot split problem)

圖片描述

爲了解決這個問題,Go 1.3採用了新的棧管理方法。

若是goroutine的棧過小了,它會去分配一塊新的更大的棧,而不是分配和釋法額外的內存空間。老的棧裏的內容被複制到新的棧裏,goroutine會在新的棧上繼續執行。在第一次調用H函數以後,將會有足夠大的棧空間,這樣之後的棧空間大小檢查都不會有問題了。這就解決了熱分裂問題。

圖片描述

變量,內聯,逃逸分析,Goroutines,和棧的分塊/複製管理就是今天我要討論的5個特性。固然Go語言高效不單單是由於這5個特性。這就像你們有千奇百怪的理由來學習Go語言同樣,這些理由也毫不止3個。

圖片描述

這些特性個個都頗有效,他們之間還相互依賴。好比,若是沒有了可衍生的棧,運行環境將多個goroutine複用到線程上面就不會頗有效。內聯在把多個小函數合併成大函數的時候也避免了棧大小的檢查開銷。逃逸分析用棧代替堆來存儲局部變量,這樣也減小來垃圾回收機制的壓力。逃逸分析還提高了緩存的性能(cache locality)。沒有可衍生的棧,逃逸分析又會對棧形成很大的壓力。

相關文章
相關標籤/搜索