先回顧下linux的內存空間佈局linux
當啓動一個C實現的thread時,C標準庫會負責分配一塊內存做爲這個線程的棧。標準庫分配這塊內存,告訴內核它的位置並讓內核處理這個線程 的執行。
在linux系統中,可經過 ulimit -s
查看系統棧大小(8M)。ulimit -s 10240
可修改棧大小爲10M。golang
這裏最大的一個問題是,分配大數組,或者循環遞歸函數時,默認的棧空間不夠用,會致使Segmentation fault
錯誤。centos
//testMaxStack.cpp #include <stdio.h> int main() { printf("init ok\n"); char a[8192*1024]; // 8M空間 printf("run over\n"); } //執行結果 [app@VM_114_13_centos c]$ ulimit -s 8192 [app@VM_114_13_centos c]$ g++ testMaxStack.cpp [app@VM_114_13_centos c]$ ./a.out Segmentation fault
解決方法有兩個:數組
ulimit -s 10240
調整標準庫給全部線程棧分配的內存塊的大小。可是全線提升棧大小意味着每一個線程都會提升棧的內存使用量,這樣一來,你將用光全部內存。Go使用的解決方案相似第二種方法。
goroutine 初始時只給棧分配很小的空間,而後隨着使用過程當中的須要自動地增加。這就是爲何Go能夠開千千萬萬個goroutine而不會耗盡內存。
Go 1.4開始使用的是連續棧,而這以前使用的分段棧。ruby
分段棧(segmented stacks)是Go語言最初用來處理棧的方案。
當建立一個goroutine時,Go運行時會分配一段8K
字節的內存用於棧供goroutine運行使 用。app
每一個go函數在函數入口處都會有一小段代碼,這段代碼會檢查是否用光了已分配的棧空間,若是用光了,這段代碼會調用morestack
函數。less
morestack函數會分配一段新內存用做棧空間,接下來它會將有關棧的各類數據信息寫入棧底的一個struct中(下圖中Stack info),包括上一段棧的地址。而後重啓goroutine,從致使棧空間用光的那個函數(下圖中的Foobar)開始執行。這就是所謂的「棧分裂 (stack split)」。函數
+---------------+
| | | unused | | stack | | space | +---------------+ | Foobar | | | +---------------+ | | | lessstack | +---------------+ | Stack info | | |-----+ +---------------+ | | | +---------------+ | | Foobar | | | | <---+ +---------------+ | rest of stack | | |
在新棧的底部,插入了一個棧入口函數lessstack
。設置這個函數用於從那個致使咱們用光棧空間的函數(Foobar)返回時用的。當那個函數(Foobar)返回時,咱們回到lessstack(這個棧幀),lessstack會查找 stack底部的那個struct,並調整棧指針(stack pointer),使得咱們返回到前一段棧空間。這樣作以後,咱們就能夠將這個新棧段(stack segment)釋放掉,並繼續執行咱們的程序了。佈局
棧縮小是一個相對代價高昂的操做。若是在一個循環中調用的函數遇到棧分裂 (stack split),進入函數時會增長棧空間(morestack 函數),返回並釋放棧段(lessstack 函數)。性能方面開銷很大。性能
go如今使用的是這套解決方案。
goroutine在棧上運行着,當用光棧空間,它遇到與舊方案中相同的棧溢出檢查。可是與舊方案採用的保留一個返 回前一段棧的link不一樣,新方案建立一個兩倍於原stack大小的新stack,並將舊棧拷貝到其中。
這意味着當棧實際使用的空間縮小爲原先的 大小時,go運行時不用作任何事情。
棧縮小是一個無任何代價的操做(棧的收縮是垃圾回收的過程當中實現的.當檢測到棧只使用了不到1/4時,棧縮小爲原來的1/2)。
此外,當棧再次增加時,運行時也無需作任何事情,咱們只須要重用以前分配的空閒空間便可。
Go語言和C不一樣,不是使用棧指針寄存器和棧基址寄存器肯定函數的棧的。
在Go的運行時庫中,每一個goroutine對應一個結構體G,大體至關於進程控制塊的概念。這個結構體中存了stackbase
和stackguard
,用於肯定這個goroutine使用的棧空間信息。每一個Go函數調用的前幾條指令,先比較棧指針寄存器跟g->stackguard
,檢測是否發生棧溢出。若是棧指針寄存器值超越了stackguard
就須要擴展棧空間。
舊棧數據複製到新棧的過程,要考慮指針失效問題。
Go實現了精確的垃圾回收,運行時知道每一塊內存對應的對象的類型信息。在複製以後,會進行指針的調整。具體作法是,對當前棧幀以前的每個棧幀,對其中的每個指針,檢測指針指向的地址,若是指向地址是落在舊棧範圍內的,則將它加上一個偏移使它指向新棧的相應地址。這個偏移值等於新棧基地址減舊棧基地址。
連接:https://www.jianshu.com/p/7ec9acca6480