【翻译】为什么 goroutine 的栈内存无穷大?
為什么80%的碼農都做不了架構師?>>> ??
一些 Go 語言的新學習者總是會對 goroutine 棧內存占用大小感到非常好奇。這一般是由于程序員進行無限的函數循環調用導致的。為了說明這個問題,請思考以下代碼示例(為使問題更加清晰而使用相對刻意的寫法):
package mainimport "fmt"type S struct {a, b int }// String 實現了接口 fmt.Stringer func (s *S) String() string {return fmt.Sprintf("%s", s) // 調用 Sprintf 時會默認調用 s.String() }func main() {s := &S{a: 1, b: 2}fmt.Println(s) }盡管我不建議你這樣做,但當你嘗試運行這段代碼的時候,你會發現你的機器正在進行大量的運算,甚至變得無響應而使你不得不使用 ctrl + c 來中斷執行,以免程序最終達到無藥可救的地步;因為我知道你會這樣做,所以我為你做好了這一步,你可以直接在 playground 執行這段代碼。
許多程序員都曾經寫過類似的代碼而導致函數的無限循環調用,并使得他們的程序崩潰,但一般情況下并不足以對他們的機器造成毀滅性破壞。問題是,為什么 Go 的程序就特殊一點的呢?
goroutine 的一個主要特性就是它們的消耗;創建它們的初始內存成本很低廉(與需要 1 至?8MB 內存的傳統 POSIX?線程形成鮮明對比)以及根據需要動態增長和縮減占用的資源。這使得 goroutine 會從 4096 字節的初始棧內存占用開始按需增長或縮減內存占用,而無需擔心資源的耗盡。
為了實現這個目標,鏈接器(5l、6l 和 8l)會在每個函數前插入一個序文,這個序文會在函數被調用之前檢查判斷當前的資源是否滿足調用該函數的需求(備注 1)。如果不滿足,則調用 runtime.morestack 來分配新的棧頁面(備注 2),從函數的調用者那里拷貝函數的參數,然后將控制權返回給調用者。此時,已經可以安全地調用該函數了。當函數執行完畢,事情并沒有就此結束,函數的返回參數又被拷貝至調用者的棧結構中,然后釋放無用的棧空間。
通過這個過程,有效地實現了棧內存的無限使用。假設你并不是不斷地在兩個棧之間往返,通俗地講叫棧分割,則代價是十分低廉的。
但是我一直注意到一個問題,當你的程序存在函數的無限循環調用而即將導致你的操作系統內存枯竭,而此時又恰好需要分配新的棧頁面,則會從堆中分配內存。
當你的函數無止盡地調用著自己,新的棧頁面會不斷地從堆中分配,繼而使得函數又能夠繼續調用自己。我相信這很快就會使程序用光你機器所有空余的物理內存,交換存儲器也會被大量使用,最終導致你的系統變得非常不穩定。
可以被 Go 使用的堆內存取決于許多方面,包括你的 CPU 架構以及操作系統,但一般依賴于你機器可用的物理內存,因此你的機器會在即將使用完堆內存之前進行大量交換存儲器的操作。
對于 Go 1.1,許多人都希望可以提升 32 位以及 64 位平臺上堆內存使用的最大限制,這個問題會在某些情況下變得更加嚴重。比如說,你的機器不太可能擁有 128GB 的物理內存(備注 3)。
最后要說的是,這里有一些 issue 已經涉及到這個問題(issue1、issue2),但仍未找到在不損失性能的情況下能夠處理該問題的一個好的解決方案。
備注:
1. 同樣適用于方法,但方法的接收者本質上就是函數的第一個參數,當討論有關 Go 的分段棧的問題時,沒有必要將它們區別對待。
2. 使用頁面這個詞不代表每次分配的內存額度是固定的 4096 字節,必要時會調用 runtime.morestack 來進行新的分配,但我猜測會與頁面值的倍數相接近。
3. 由于 Go 1.1 的改動,64 位 Windows 平臺的堆內存被限制在 32GB 之內。
原文地址:http://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite
?
轉載于:https://my.oschina.net/Obahua/blog/144549
總結
以上是生活随笔為你收集整理的【翻译】为什么 goroutine 的栈内存无穷大?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CentOS6.4
- 下一篇: linux中通过命令生成hex值