Golang协程栈初始化

丁凯 · · 42 次点击 · · 开始浏览    

主协程初始化

Golang的主协程指的是运行main函数的协程,而子协程指的是在程序运行过程中由主协程创建的协程。每个线程(m)只会有一个主协程,而子协程可能会有很多很多。

子协程和主协程在概念和内部实现上几乎没有任何区别,唯一的不同在于它们的初始栈大小不同。

我们先看看测试过程中生成的主协程堆栈示例。我测试代码中就生成了一个主协程,通过反汇编代码看到他的样子大概如下:

主协程启动

分析连接器(libinit())发现go程序的入口函数是_rt0_amd64_linux(linux amd64机器)

子协程初始化

Golang子协程堆栈在协程被创建时也一并创建,代码如下:

func newproc1(fn *funcval, argp *uint8, narg int32, nret int32, callerpc uintptr) *g {
    _g_ := getg()

    ......

    _p_ := _g_.m.p.ptr()
    newg := gfget(_p_)
    if newg == nil {
        // 创建协程栈
        newg = malg(_StackMin)
        casgstatus(newg, _Gidle, _Gdead)
        allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack.
    }
    ......

    totalSize := 4*regSize + uintptr(siz) // extra space in case of reads slightly beyond frame
    if hasLinkRegister {
        totalSize += ptrSize
    }
    totalSize += -totalSize & (spAlign - 1) // align to spAlign
    // 新协程的栈顶计算,将栈的基地址减去参数占用的空间
    sp := newg.stack.hi - totalSize
    spArg := sp
    if hasLinkRegister {
        // caller's LR
        *(*unsafe.Pointer)(unsafe.Pointer(sp)) = nil
        spArg += ptrSize
    }
    ...
    // 设置新建协程的栈顶sp
    newg.sched.sp = sp
}

// Allocate a new g, with a stack big enough for stacksize bytes.
func malg(stacksize int32) *g {
    newg := new(g)
    if stacksize >= 0 {
        stacksize = round2(_StackSystem + stacksize)
        systemstack(func() {
            newg.stack, newg.stkbar = stackalloc(uint32(stacksize))
        })
        // 设置stackguard,在协程栈不够用时再重新申请新的栈
        newg.stackguard0 = newg.stack.lo + _StackGuard
        newg.stackguard1 = ^uintptr(0)
        newg.stackAlloc = uintptr(stacksize)
    }
    return newg
}

在go1.5.1版本中,_StackMin大小被定义为2048(而在1.3.2版本中该值还是8192),也即每个协程的初始堆栈大小为2KB,相当小了。缩小该值的好处是即使创建了很多的协程也不会导致内存使用的急剧增长。

另外,在协程栈空间被分配出来后,还需要作一些其他的初始化,主要是协程栈顶的设置以及堆栈保护的设置。

  • 栈顶的设置方法比较简单,将当前栈的起始地址减去参数占用的空间即可(注意栈是从高地址向低地址延伸的)。

  • 栈保护的设置指的是设置一个临界点,当sp到达该临界点时认为栈空间可能会不足,需要进行栈扩容。当前版本的协程栈保护大小事640B。

以下是我对源码加入一些调试信息后打印的协程创建堆栈的详细信息:

create goroutine, stack:{stackLow: 859530534912, stackHi:859530536928, sp:859530536880, stackguard:859530535552}

根据上面的分析以及打印信息可以大致勾勒出协程初始化状态的堆栈:

https://tracymacding.gitbooks.io/implementation-of-golang/stack/stack_overview.html   https://tracymacding.gitbooks.io/implementation-of-golang/stack/stack_enlarge.html

本文来自:知乎专栏

感谢作者:丁凯

查看原文:Golang协程栈初始化

42 次点击  
加入收藏 微博
被以下专栏收入,发现更多相似内容
下一篇:协程栈扩容
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传