#分段栈在Go的1.3版本之前,使用的栈结构是分段栈。随着goroutine调用的函数层级加深或者需要的局部变量越来越多,运行时会调用runtime.morestack和runtime.newstack创建新的栈空间,这些栈空间是不连续的,但是当前goroutine的多个栈空间会以双向链表的形式串联起来,运行时会通过指针找到连续的栈片。分段栈虽然可以按需为当前goroutine分配内存,及时减少内存占用,但是它也有一个比较大的问题:如果当前goroutine栈快满了,任何函数调用都会触发扩栈。当函数返回后,会触发栈的收缩。如果在循环中调用函数,栈的分配和释放会造成巨大的额外开销。这被称为热分裂问题(Hotsplit)。为了解决这个问题,Go不得不在1.2版本中将栈的初始化内存从4KB增加到8KB。后来采用连续栈结构后,初始栈大小减小到2KB。#连续栈连续栈可以解决分段栈中的两个问题。核心原理是每当程序的栈空间不足时,初始化一个两倍于旧栈大小的新栈,并将原栈中的所有值迁移到新栈中,有足够的内存空间用于新栈局部变量或函数调用。当使用连续栈机制时,栈空间不足导致的扩充会经过以下几个步骤:调用runtime.newstack在内存空间中分配更大的栈内存空间;使用runtime.copystack将旧栈的所有内容复制到新栈中;将旧栈对应变量的指针指向新栈;调用runtime.stackfree销毁回收旧栈的内存空间;copystack会将旧栈的所有内容复制到新栈中,然后调整所有指向旧栈的指针变量的指针指向新栈。我们可以用下面的程序来验证一下,同一个变量的内存地址在扩栈后会发生变化。packagemainfuncmain(){varx[10]intprintln(&x)a(x)println(&x)}//go:noinlinefunca(x[10]int){println(`funca`)vary[100]intb(y)}//go:noinlinefuncb(x[100]int){println(`funcb`)vary[1000]intc(y)}//go:noinlinefuncb(x[1000]int){println(`funcc`)}程序输出你可以看到扩栈前后变量x的内存地址的变化:0xc000030738...0xc000081f38是不是很简单?.转载本文请联系围棋编程时间公众号。
