美文网首页
goroutine的诞生

goroutine的诞生

作者: freedom117 | 来源:发表于2022-09-16 14:20 被阅读0次

    参见代码// src/runtime/proc.go

    go func() {
    // 要做的事
    }()
    

    就启动了一个 goroutine 的时候,一定要知道,在 Go 编译器的作用下,这条语句最终会转化成 newproc 函数。

    func newproc1(fn *funcval, argp unsafe.Pointer, narg int32, callergp *g, callerpc uintptr) {
     // 当前 goroutine 的指针
      // 因为已经切换到 g0 栈,所以无论什么场景都是 _g_ = g0
      // g0 是指当前工作线程的 g0    
    _g_ := getg()
    
        if fn == nil {
            _g_.m.throwing = -1 // do not dump full stacks
            throw("go of nil func value")
        }
        acquirem() // disable preemption because it can be holding p in a local var
     // 参数加返回值所需要的空间(经过内存对齐)
        siz := narg
        siz = (siz + 7) &^ 7
    
        // We could allocate a larger initial stack if necessary.
        // Not worth it: this is almost always an error.
        // 4*sizeof(uintreg): extra space added below
        // sizeof(uintreg): caller's LR (arm) or return address (x86, in gostartcall).
        if siz >= _StackMin-4*sys.RegSize-sys.RegSize {
            throw("newproc: function arguments too large for new goroutine")
        }
    // 当前工作线程所绑定的 p
      // 初始化时 _p_ = g0.m.p,也就是 _p_ = allp[0]
        _p_ := _g_.m.p.ptr()
    // 从 p 的本地缓冲里获取一个没有使用的 g,初始化时为空,返回 nil
        newg := gfget(_p_)
        if newg == nil {
    // new 一个 g 结构体对象,然后从堆上为其分配栈,并设置 g 的 stack 成员和两个 stackgard 成员
            newg = malg(_StackMin)
    // 初始化 g 的状态为 _Gdead
            casgstatus(newg, _Gidle, _Gdead)
    // 放入全局变量 allgs 切片中
            allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack.
        }
        if newg.stack.hi == 0 {
            throw("newproc1: newg missing stack")
        }
    
        if readgstatus(newg) != _Gdead {
            throw("newproc1: new g is not Gdead")
        }
    // 计算运行空间大小,对齐
        totalSize := 4*sys.RegSize + uintptr(siz) + sys.MinFrameSize // extra space in case of reads slightly beyond frame
        totalSize += -totalSize & (sys.SpAlign - 1)                  // align to spAlign
    // 确定 sp 位置
        sp := newg.stack.hi - totalSize
    // 确定参数入栈位置
        spArg := sp
        if usesLR {
            // caller's LR
            *(*uintptr)(unsafe.Pointer(sp)) = 0
            prepGoExitFrame(sp)
            spArg += sys.MinFrameSize
        }
        if narg > 0 {
    //  为新创建的栈指定内容 将参数从执行 newproc 函数的栈拷贝到新 g 的栈 
            memmove(unsafe.Pointer(spArg), argp, uintptr(narg))
            // This is a stack-to-stack copy. If write barriers
            // are enabled and the source stack is grey (the
            // destination is always black), then perform a
            // barrier copy. We do this *after* the memmove
            // because the destination stack may have garbage on
            // it.
            if writeBarrier.needed && !_g_.m.curg.gcscandone {
                f := findfunc(fn.fn)
                stkmap := (*stackmap)(funcdata(f, _FUNCDATA_ArgsPointerMaps))
                if stkmap.nbit > 0 {
                    // We're in the prologue, so it's always stack map index 0.
                    bv := stackmapdata(stkmap, 0)
                    bulkBarrierBitmap(spArg, spArg, uintptr(bv.n)*sys.PtrSize, 0, bv.bytedata)
                }
            }
        }
    // 把 newg.sched 结构体成员的所有成员设置为 0
        memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched))
    // 设置 newg 的 sched 成员,调度器需要依靠这些字段才能把 goroutine 调度到 CPU 上运行
        newg.sched.sp = sp
        newg.stktopsp = sp
    // newg.sched.pc 表示当 newg 被调度起来运行时从这个地址开始执行指令
        newg.sched.pc = funcPC(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function
        newg.sched.g = guintptr(unsafe.Pointer(newg))
        gostartcallfn(&newg.sched, fn)
        newg.gopc = callerpc
        newg.ancestors = saveAncestors(callergp)
    // 设置 newg 的 startpc 为 fn.fn,该成员主要用于函数调用栈的 traceback 和栈收缩
    // newg 真正从哪里开始执行并不依赖于这个成员,而是 sched.pc
        newg.startpc = fn.fn
        if _g_.m.curg != nil {
            newg.labels = _g_.m.curg.labels
        }
        if isSystemGoroutine(newg, false) {
            atomic.Xadd(&sched.ngsys, +1)
        }
    // 设置 g 的状态为 _Grunnable,可以运行了
        casgstatus(newg, _Gdead, _Grunnable)
    
        if _p_.goidcache == _p_.goidcacheend {
            // Sched.goidgen is the last allocated id,
            // this batch must be [sched.goidgen+1, sched.goidgen+GoidCacheBatch].
            // At startup sched.goidgen=0, so main goroutine receives goid=1.
            _p_.goidcache = atomic.Xadd64(&sched.goidgen, _GoidCacheBatch)
            _p_.goidcache -= _GoidCacheBatch - 1
            _p_.goidcacheend = _p_.goidcache + _GoidCacheBatch
        }
    // 设置 goid
        newg.goid = int64(_p_.goidcache)
        _p_.goidcache++
        if raceenabled {
            newg.racectx = racegostart(callerpc)
        }
        if trace.enabled {
            traceGoCreate(newg, newg.startpc)
        }
    // 将 G 放入 _p_ 的本地待运行队列
        runqput(_p_, newg, true)
    
        if atomic.Load(&sched.npidle) != 0 && atomic.Load(&sched.nmspinning) == 0 && mainStarted {
            wakep()
        }
        releasem(_g_.m)
    }
    

    执行go 之后会在go的p0上面获取一个空闲的gorountine 或者创建一个新的gorountine数据结构,加入到allgs 中,在gorountine的sp栈顶处传入一个 funcval 对象 里面有要执行的方法,让后在g的sche对象中设置sp的位置与pc的地址。将g的状态设置为_Grunnable,设置goid(goid是递增的) 并且加入p的本地运行队列。

    相关文章

      网友评论

          本文标题:goroutine的诞生

          本文链接:https://www.haomeiwen.com/subject/toexjltx.html