美文网首页
Go sheduler 开始调度循环

Go sheduler 开始调度循环

作者: freedom117 | 来源:发表于2022-09-16 14:20 被阅读0次
    
    func mstart1() {
    
    // 启动过程时 _g_ = m0.g0
        _g_ := getg()
    
        if _g_ != _g_.m.g0 {
            throw("bad runtime·mstart")
        }
    
        // Record the caller for use as the top of stack in mcall and
        // for terminating the thread.
        // We're never coming back to mstart1 after we call schedule,
        // so other calls can reuse the current frame.
    // 一旦调用 schedule() 函数,永不返回
    // 所以栈帧可以被复用
        save(getcallerpc(), getcallersp())
        asminit()
        minit()
    
        // Install signal handlers; after minit so that minit can
        // prepare the thread to be able to handle the signals.
        if _g_.m == &m0 {
            mstartm0()
        }
    
    // 执行启动函数。初始化过程中,fn == nil
        if fn := _g_.m.mstartfn; fn != nil {
            fn()
        }
    
        if _g_.m != &m0 {
            acquirep(_g_.m.nextp.ptr())
            _g_.m.nextp = 0
        }
    // 进入调度循环。永不返回
        schedule()
    }
    

    更新到g0的pc 和sp 中,前者指向 mstart1 函数栈上参数的位置,后者则指向 gosave 函数 。

    func save(pc, sp uintptr) {
        _g_ := getg()
    
        _g_.sched.pc = pc
        _g_.sched.sp = sp
        _g_.sched.lr = 0
        _g_.sched.ret = 0
        _g_.sched.g = guintptr(unsafe.Pointer(_g_))
        // We need to ensure ctxt is zero, but can't have a write
        // barrier here. However, it should always already be zero.
        // Assert that.
        if _g_.sched.ctxt != nil {
            badctxt()
        }
    }
    

    接下来,进入 schedule 函数

    // 执行一轮调度器的工作:找到一个 runnable 的 goroutine,并且执行它
    // 永不返回
    func schedule() {
        // _g_ = 每个工作线程 m 对应的 g0,初始化时是 m0 的 g0
        _g_ := getg()
    
        // ……………………
    
    top:
        // ……………………
    
        var gp *g
        var inheritTime bool
    
        // ……………………
    
        if gp == nil {
            // Check the global runnable queue once in a while to ensure fairness.
            // Otherwise two goroutines can completely occupy the local runqueue
            // by constantly respawning each other.
            // 为了公平,每调用 schedule 函数 61 次就要从全局可运行 goroutine 队列中获取
            if _g_.m.p.ptr().schedtick%61 == 0 && sched.runqsize > 0 {
                lock(&sched.lock)
                // 从全局队列最大获取 1 个 gorutine
                gp = globrunqget(_g_.m.p.ptr(), 1)
                unlock(&sched.lock)
            }
        }
    
        // 从 P 本地获取 G 任务
        if gp == nil {
            gp, inheritTime = runqget(_g_.m.p.ptr())
            if gp != nil && _g_.m.spinning {
                throw("schedule: spinning with local work")
            }
        }
        
        if gp == nil {
            // 从本地运行队列和全局运行队列都没有找到需要运行的 goroutine,
            // 调用 findrunnable 函数从其它工作线程的运行队列中偷取,如果偷不到,则当前工作线程进入睡眠
            // 直到获取到 runnable goroutine 之后 findrunnable 函数才会返回。
            gp, inheritTime = findrunnable() // blocks until work is available
        }
    
        // This thread is going to run a goroutine and is not spinning anymore,
        // so if it was marked as spinning we need to reset it now and potentially
        // start a new spinning M.
        if _g_.m.spinning {
            resetspinning()
        }
    
        if gp.lockedm != nil {
            // Hands off own p to the locked m,
            // then blocks waiting for a new p.
            startlockedm(gp)
            goto top
        }
    
        // 执行 goroutine 任务函数
        // 当前运行的是 runtime 的代码,函数调用栈使用的是 g0 的栈空间
        // 调用 execute 切换到 gp 的代码和栈空间去运行
        execute(gp, inheritTime)
    }
    

    调度器的工作内容就是,选择一个在全局队列(每61次选一次全局)或者本地队列或者其他队列中偷取的方式,选择一个gorountine出来,让后调用execute方法执行它。
    func execute(gp *g, inheritTime bool) {
    g := getg()

    // Assign gp.m before entering _Grunning so running Gs have an
    // M.
    

    //将 gp 和 m 关联起来
    g.m.curg = gp
    gp.m = g.m
    //gp的状态切换成_Grunnable
    casgstatus(gp, _Grunnable, _Grunning)
    gp.waitsince = 0
    gp.preempt = false
    gp.stackguard0 = gp.stack.lo + _StackGuard
    //如果不继承事件 调度器的调度次数++
    if !inheritTime {
    g.m.p.ptr().schedtick++
    }

    // Check whether the profiler needs to be turned on or off.
    hz := sched.profilehz
    if _g_.m.profilehz != hz {
        setThreadCPUProfiler(hz)
    }
    
    if trace.enabled {
        // GoSysExit has to happen when we have a P, but before GoStart.
        // So we emit it here.
        if gp.syscallsp != 0 && gp.sysblocktraced {
            traceGoSysExit(gp.sysexitticks)
        }
        traceGoStart()
    }
    //gogo 完成从g0到gp的真正转换
    

    //cpu执行权的转让以及栈的来回切换
    //执行流切换的本质是 cpu寄存器以及函数调用栈的切换
    //高级语言搞不定只能用汇编
    gogo(&gp.sched)
    }
    gogo的本质就是替换sp和pc为gp的,执行gp的pc指向的方法。


    image.png

    相关文章

      网友评论

          本文标题:Go sheduler 开始调度循环

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