上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
4.5.1 调度
调度函数除在各级队列查找任务外,还要处理垃圾回收、锁定等情况。
// proc.go // One round of scheduler: find a runnable goroutine and execute it. // Never returns. func schedule() { _g_ := getg() // 检查当前 M 是否被特定 G 锁定。 // 交出 P,休眠 M,直到其他人发现锁定任务后唤醒执行。 if _g_.m.lockedg != 0 { stoplockedm() execute(_g_.m.lockedg.ptr(), false) // Never returns. } top: pp := _g_.m.p.ptr() pp.preempt = false // STW 标记。 if sched.gcwaiting != 0 { gcstopm() goto top } // 执行安全函数,比如 forEachP。 if pp.runSafePointFn != 0 { runSafePointFn() } // 检查并执行定时器。 checkTimers(pp, 0) var gp *g var inheritTime bool // 启动垃圾回收任务。 if gp == nil && gcBlackenEnabled != 0 { gp = gcController.findRunnableGCWorker(_g_.m.p.ptr()) if gp != nil { tryWakeP = true } } // 每隔 60 次,尝试从全局队列提取任务,以确保公平。 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. if _g_.m.p.ptr().schedtick%61 == 0 && sched.runqsize > 0 { lock(&sched.lock) gp = globrunqget(_g_.m.p.ptr(), 1) unlock(&sched.lock) } } // 从本地任务队列提取。 if gp == nil { gp, inheritTime = runqget(_g_.m.p.ptr()) // We can see gp != nil here even if the M is spinning, // if checkTimers added a local goroutine via goready. } // 从其他可能的地方提取。 if gp == nil { gp, inheritTime = findrunnable() // blocks until work is available } // 找到任务,解除自旋状态。 if _g_.m.spinning { resetspinning() } // 找到锁定任务,移交 P 给锁定 M 去执行。 // 自己休眠。一旦被唤醒,则重新开始调度循环。 if gp.lockedm != 0 { // Hands off own p to the locked m, // then blocks waiting for a new p. startlockedm(gp) goto top } // 执行任务。 execute(gp, inheritTime) }
如返回的是 runnext,则 inheritTime = True
。表示继承上个任务时间片,不累加执行计数器(P.schedtick),这会减少对全局队列的检查。
另一方面,sysmon 会比对不同时段计数器,检查是否执行了新任务。以此判断任务执行时间是否过长(10 ms),是否需要抢占调度。
在
GODEBUG=scheddetail
输出里可以看到计数器信息。
单个 G 未必在一次调度内结束。因某些原因中断,被重新放回任务队列,等待断点恢复。
MP 切换任务前,会将执行状态(PC、SP)保存到 G.sched,其他寄存器数据保存到 G.stack。
切换本身(G100->G0->G101)消耗并不大,关键在于如何查找下一个 G 任务。
- 阻塞:block,syscall、mutex、channel。
- 抢占:preempt,sysmon。
锁定
某些 G (syscall、cgo)需要与特定 M 锁定。
一旦锁定,M 会休眠直到 G 出现,并执行。
同理,G 只能在锁定 M 上执行。
这么做是为了维持 M 的某些特殊状态(比如 signal 等)
// proc.go // LockOSThread wires the calling goroutine to its current operating system thread. // The calling goroutine will always execute in that thread, // and no other goroutine will execute in it, // until the calling goroutine has made as many calls to // UnlockOSThread as to LockOSThread. // If the calling goroutine exits without unlocking the thread, // the thread will be terminated. // // All init functions are run on the startup thread. Calling LockOSThread // from an init function will cause the main function to be invoked on // that thread. // // A goroutine should call LockOSThread before calling OS services or // non-Go library functions that depend on per-thread state. func LockOSThread() { _g_ := getg() _g_.m.lockedExt++ dolockOSThread() } func lockOSThread() { getg().m.lockedInt++ dolockOSThread() }
// dolockOSThread is called by LockOSThread and lockOSThread below // after they modify m.locked. Do not allow preemption during this call, // or else the m might be different in this function than in the caller. func dolockOSThread() { _g_ := getg() _g_.m.lockedg.set(_g_) // 存放位置! _g_.lockedm.set(_g_.m) }
在调度函数头部,一旦检查到当前 M 被某个 G 锁定,那么会进入休眠状态,直到目标 G 出现。
// Stops execution of the current m that is locked to a g until the g is runnable again. func stoplockedm() { _g_ := getg() // 交出 P 给其他 M 去执行任务。 if _g_.m.p != 0 { // Schedule another M to run this p. _p_ := releasep() handoffp(_p_) } // Wait until another thread schedules lockedg again. mPark() // 被其他 MP 唤醒,关联它转交的 P。 acquirep(_g_.m.nextp.ptr()) _g_.m.nextp = 0 }
其他 MP 找到锁定 G 时,会交出自己的 P,唤醒该任务关联 M。
// Schedules the locked m to run the locked gp. func startlockedm(gp *g) { _g_ := getg() // 该任务锁定的 M。 mp := gp.lockedm.ptr() // 转交自己的 P 给锁定 M,并唤醒它。 _p_ := releasep() mp.nextp.set(_p_) notewakeup(&mp.park) // 当前 M 休眠。 stopm() }
为什么等待别人转交,而不是锁定 M 自己找?MP 创建锁定 G,执行期间可能因为某些原因(syscall)暂停。此时,P 被转交给其他人去执行任务,当 M 醒来时,已失去原 P,也就不方便从其任务队列提取。更何况,该 G 还可能被转移到全局队列,或被偷窃。如果直接从 M.lockedg 提取,那么如何处理其存放队列?不如,等它出现后移交。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论