关于go:golang-如何处理栈

44次阅读

共计 1650 个字符,预计需要花费 5 分钟才能阅读完成。

前言

咱们先看看其余语言是如何解决栈,在 Java 中,默认的栈大小是 1M,能够通过 -XX:ThreadStackSize 参数管制, 当新建一个线程时,会向内核申请指定的栈大小。然而固定的栈大小如果设置过大,可能会节约内存,设置过小,可能导致栈溢出。在 golang 语言中,goroutine 是轻量的用户级协程,使用者不须要有很大的累赘,不必像 Java,C++ 一样设置线程池来解决工作。所以一个利用能够开启上万个 goroutine。go 采纳的就是按需分配,默认每个栈大小为 2k, 当空间不够,能够 “ 栈增长 ”。

一个协程的栈空间次要对应办法执行过程中的栈帧的压栈出栈。每个办法对应一个栈帧

int a(int m, int n) {if (m == 0) {return n + 1;} else if (m > 0 && n == 0) {return a(m - 1, 1);
    } else {return a(m - 1, a(m, n - 1));
    }
}

如下办法将产生极大的递归调用,从而导致栈空间特地大,如果采纳固定栈大小,和容易栈溢出。

golang 在晚期采纳分段栈的形式来进行栈增长,然而会带来 hot split 问题,栈拷贝解决了该问题,目前默认是采纳栈拷贝。

分段栈(segment stack)

goroutine 初始化是默认是 2k 的栈空间,那么是如何检测到栈空间不够?其实编译器会在每个办法的入口处, 插入一个办法 morestack判断是否须要栈增长,当栈帧一直出栈,goroutine 曾经不须要应用那么多栈空间,编译器在办法的返回处插入一个办法 lessstack 用来判断是否膨胀栈大小。

如下图所示,当执行 Foobar 办法时,进行了栈增长,那么此时会栈决裂(stack split), 在新的栈分段的栈底压入一个 stack info , 记入上一个栈分段的信息(包含地址等),而后压入 lessstack 和 Foobar 栈帧,若 Foobar 执行完并没有新的栈帧入栈,那么会执行 lessstack 进行膨胀。

go build -gcflags -S main.go // 查看汇编代码,能够看到 morestack

  +---------------+
  |               |
  |   unused      |
  |   stack       |
  |   space       |
  +---------------+
  |    Foobar     |
  |               |
  +---------------+
  |               |
  |  lessstack    |
  +---------------+
  | Stack info    |
  |               |-----+
  +---------------+     |
                        |
                        |
  +---------------+     |
  |    Foobar     |     |
  |               | <---+
  +---------------+
  | rest of stack |
  |               |

Hot split?

如上若是,如果 Foobar 办法是在一个循环中调用,那么会导致该 goroutine 的栈空间频繁的增长和膨胀,这个对性能损耗是极大的,这个就是 hot split 问题。咱们来看看栈拷贝是如何解决

间断栈(Contiguous stack)

间断栈又叫栈拷贝(stack copying)

栈拷贝栈增长和分段栈是一样的解决,不同的是,它是申请一个栈大小是原来的两倍,而后将原来的栈上的数据拷贝过去。这样就不会有 hot split 问题。不过在 GC 时,当栈调配空间大于理论应用大小,也会进行栈膨胀。

咱们晓得,指向栈上的数据的指针肯定是存在栈上,而不是在堆上,如果有堆上的指针指向栈空间,那么随着栈帧出栈,可能对导致堆上呈现悬挂指针(一个指针指向了未定义的中央),这是不容许的。

因为 go 的 GC 须要晓得指针在什么中央,所以咱们能够借助 GC 的信息,将栈上的指针挪动到新的地位(新申请栈空间上对应的地位)以及所有相干的栈上指针都须要解决。

因为局部 runtime 上的代码还是应用 c 语言编写,所以没有指针相干的信息,所以他们是不可拷贝的,也就不能应用栈拷贝的形式,所以须要用回分段栈。这也是为什么当初 golang 团队在用 golang 重写 runtime 模块。

参考

  1. How Stacks are Handled in Go
  2. Go: How Does the Goroutine Stack Size Evolve?

正文完
 0