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