golang的堆栈跟C++的堆栈是有所不同的,golang的栈是在堆上的,而C++的堆栈是分开的,golang协程栈的作用是记录协程的执行路径,局部变量,函数传参和函数返回值。简单的步骤如下:
另外,go的协程栈初始空间还是比较小的,只有2k到4k,那么当我们遇到局部变量太大,栈帧太多的时候,栈空间不够用了怎么办呢??
这里就涉及到逃逸分析了。
不是所有的变量都能放在协程栈上的。栈帧回收时,需要继续使用的变量这时就会逃逸到堆上去。然后变量太大也会逃逸到堆上去。
逃逸也分指针逃逸,空接口逃逸和大变量逃逸。
指针逃逸也很容易判断,比如下列函数,返回的是个指针,因为返回值在其他的函数还会用到,所以这时会逃逸到堆上。
空接口逃逸:如果函数参数为interface{},那么函数的实参很可能会逃逸,这个有点难分析,比如下列代码,你会以为i是实打实的局部变量,只在b函数里面使用,并不会逃逸,那么就错了。为什么?因为我们调用了Println函数,我们来看看Println的源代码,他的接收参数是空接口类型,那么就很有可能会逃逸,因为interface{}类型的函数往往会使用反射来看一下我传的值是什么类型的,用到反射就往往会逃逸到堆上了,因为在栈上很难做反射。
大变量逃逸:过大的变量会导致栈空间不足,那就会让他逃逸到堆上。在64位机器中,一般超过64KB的变量会逃逸。
通过逃逸我们能够解决大变量,那么栈帧太多怎么办呢?其实也没啥好办法,也就是栈扩容。在函数调用前会判断栈空间是否足够,如果不足够呢就会进行栈扩容,1.13之前使用的是分段栈方法,之后用的连续栈方法。