前面介绍的scheduler和channel里面都与gopark和goready这两个函数紧密相关,但是站在上层可以理解这两个函数的作用,但是出于对源码探索,我们要明白这两个函数不仅仅做了啥,还要知道怎么做的。本文主要内容是从底层源码分析这两个函数原理:
- gopark函数
- goready函数
gopark函数
gopark函数在协程的实现上扮演着非常重要的角色,用于协程的切换,协程切换的原因一般有以下几种情况:
- 系统调用;
- channel读写条件不满足;
- 抢占式调度时间片结束;
gopark函数做的主要事情分为两点:
- 解除当前goroutine的m的绑定关系,将当前goroutine状态机切换为等待状态;
- 调用一次schedule()函数,在局部调度器P发起一轮新的调度。
下面我们来研究一下gopark函数是怎么实现协程切换的。
先看看源码:
func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceEv byte, traceskip int) {
if reason != waitReasonSleep {
checkTimeouts() // timeouts may expire while two goroutines keep the scheduler busy
}
mp := acquirem()
gp := mp.curg
status := readgstatus(gp)
if status != _Grunning && status != _Gscanrunning {
throw("gopark: bad g status")
}
mp.waitlock = lock
mp.waitunlockf = *(*unsafe.Pointer)(unsafe.Pointer(&unlockf))
gp