【Go笔记】函数与切片

使用

  • 函数是一类公民
  • 可以作为变量,参数传递、返回、以及赋值
  • 多返回值,返回结果和错误

闭包与陷阱

闭包

  • 包含函数的入口地址和其关联的环境
  • 闭包可以引用闭包外的变量

陷阱

1
2
3
4
5
6
values := []string{"a", "b", "c", "d", "e"}
for _, val := range values {
go func() {
fmt.Println(val)
}()
}
  • 这个程序里面,不同的协程获取的由于是val的内存地址,而range会使得val地址上的值不断刷新,因此输出的不一定是遍历。
  • 可以改为闭包函数传参,从而解决这个问题
1
2
3
4
5
6
values := []string{"a", "b", "c", "d", "e"}
for _, val := range values {
go func(val string) {
fmt.Println(val)
}(val)
}

函数栈

  • 遵循LIFO(后进先出)的规则,增长方向是从高地址到低地址
  • 函数执行时,参数,返回地址,局部变量会被压入,退出时弹出
  • 每个命令执行的时候使用内存的一片区域,为栈帧
  • 调用函数时,栈帧从高到低的结构为BP --> 传参 --> 返回值 --> 保存返回值的变量 --> 返回地址
  • 函数栈只保存返回地址,参数以及必要外围信息,函数本身代码保存在代码区中,和栈帧分开

栈扩容和栈转移

栈扩容

  • 每个协程都有一个栈,初始化为2KB
  • 64位系统的栈最大为1GB,32位为250MB
  • 在序言阶段判断是否需要对栈进行扩容,通过比较寄存器rspstackguard0的大小,若rsp小,那就有扩容的需要
  • 扩容时,新栈的大小是老栈的两倍,然后再将数据从老栈转移到新栈上面

栈转移

  • 首先设定状态为_Gcopystack,让垃圾回收不要扫描
  • 分配新栈的内存,在Linux下,对于2/4/8/16KB的小栈会有专门的优化,在每个逻辑处理器中预先分配缓冲池,回收的时候回到缓冲池中
  • 对于大栈来说,如果缓冲池中没有对应大小的栈,那就从堆中去拿
  • 分配到新栈之后,如果有指针指向旧栈,那么就要调整到新栈去,涉及到内存复制

栈调试

  • 可以从修改Go的源码进行源码层面的栈调试,如修改stackDebug为1来调试
  • 也可以使用pprof标准库来调试,获取CPU瓶颈之类的信息

【Go笔记】函数与切片
https://study.0x535a.cn/go-note/go-func-stack/
Author
Stephen Zeng
Posted on
August 18, 2025
Licensed under