$GOROOT\src\runtime\proc.go
runtime.LockOSThread()
runtime.UnlockOSThread()
其实注释中比较详细了,这里简化说下
- 将当前的G 和当前的 操作系统线程 M绑定
- 绑定后,G只能在这个M上运行,这个M也只能运行这个G
- 解除绑定的方法是,调用 与LockOSThread次数相同的 UnlockOSThread
- 如果处于LockOSThread的G退出了,那么与其绑定的M会被退出
- 如果在任何init函数中调用LockOSThread,会导致main函数和M绑定(因为runtime.main也是一个Goroutine,这个G会先执行init函数,并调用用户的main函数)
- 主要用途是在依赖 每线程独立状态(per-thread state)时,进行LockOSThread
- ps:仍然是需要P去执行的,可以重复LockOSThread
func LockOSThread() {
if atomic.Load(&newmHandoff.haveTemplateThread) == 0 && GOOS != "plan9" {
startTemplateThread()
}
_g_ := getg()
// 将M的lockedExt字段计数加一
_g_.m.lockedExt++
if _g_.m.lockedExt == 0 {
_g_.m.lockedExt--
panic("LockOSThread nesting overflow")
}
dolockOSThread()
}
func dolockOSThread() {
if GOARCH == "wasm" {
return // no threads on wasm yet
}
_g_ := getg()
// 设置M的lockedg字段为G
_g_.m.lockedg.set(_g_)
// 设置G的lockedm字段为M
_g_.lockedm.set(_g_.m)
}
func UnlockOSThread() {
_g_ := getg()
// 如果当前没有被锁定,直接返回
if _g_.m.lockedExt == 0 {
return
}
// 将M的lockedExt字段计数减一
_g_.m.lockedExt--
dounlockOSThread()
}
func dounlockOSThread() {
if GOARCH == "wasm" {
return // no threads on wasm yet
}
_g_ := getg()
// 如果lockedExt仍有绑定计数时,也直接返回(多次LockOSThread,没有完全解锁)
if _g_.m.lockedInt != 0 || _g_.m.lockedExt != 0 {
return
}
_g_.m.lockedg = 0 // 清空M中的lockedg标志
_g_.lockedm = 0 // 清空G中的lockedm标志
}
继续分析,如果做到“G只能在这个M上运行,这个M也只能运行这个G”
LockOSThread时设置了两个状态 m.lockedg 、 g.lockedm,在schedule中会使用这两个字段进行判断
func schedule() {
if _g_.m.lockedg != 0 { // 如果一个 LockOSThread 的M进入调度
stoplockedm() // 停止当前M,“死等”与其绑定的G
execute(_g_.m.lockedg.ptr(), false) // 执行与其绑定的G.
}
……
top:
……
if gp.lockedm != 0 { // 如果一个 LockOSThread的G进入调度
// 将当前M的P交出给 与G绑定的M,去运行这个G,唤醒与G绑定的M,然后停止当前M
startlockedm(gp)
goto top
}
execute(gp, inheritTime)
}
func stoplockedm() {
_g_ := getg()
if _g_.m.p != 0 {
// Schedule another M to run this p.
_p_ := releasep()
handoffp(_p_)
}
incidlelocked(1)
// Wait until another thread schedules lockedg again.
notesleep(&_g_.m.park) // 等待被唤醒
------------------------------------
noteclear(&_g_.m.park) // 被唤醒
status := readgstatus(_g_.m.lockedg.ptr())
acquirep(_g_.m.nextp.ptr()) // 获取P
_g_.m.nextp = 0
// 回到调度,执行与其绑定的G
}
func startlockedm(gp *g) {
_g_ := getg()
mp := gp.lockedm.ptr()
// directly handoff current P to the locked m
incidlelocked(-1)
_p_ := releasep()
mp.nextp.set(_p_) // 将当前P直接传递给 与G绑定的M,让其去运行
notewakeup(&mp.park) // 唤醒与G绑定的M(在上方 notesleep(&_g_.m.park) 中等待)
stopm()
}
如何做到 “如果处于LockOSThread的G退出了,那么与其绑定的M会被退出”
这里参考 qcrao的一篇文章千难万险 —— goroutine 从生到死(六)
在创建G的时候,会在栈底埋入
goexit
,G退出时,会自动跳到goexit
中,并进一步跳转到goexit0
,正常情况下会再次进入调度循环。
func goexit0(gp *g) {
_g_ := getg()
locked := gp.lockedm != 0 // 先缓存G是否被 LockOSThread
gp.lockedm = 0 // 清空LockOSThread标志
_g_.m.lockedg = 0 // 清空LockOSThread标志
……
dropg()
gfput(_g_.m.p.ptr(), gp)
if locked {
// 如果是G退出时仍处于LockOSThread状态
// 直接让M退出,而不是进入runtime调度线程池(这里应该只是个策略选择问题,这个M中可能仍缓存了一些 “每线程独立状态”,所以让其退出避免复用时出现冲突)
// 调用gogo(&_g_.m.g0.sched) 后,会回到 启动M时的mstart函数,进而触发结束当前M的流程
if GOOS != "plan9" { // See golang.org/issue/22227.
gogo(&_g_.m.g0.sched)
} else {
// Clear lockedExt on plan9 since we may end up re-using
// this thread.
_g_.m.lockedExt = 0
}
}
schedule()
}
本文由 LeonardWang 创作,采用 知识共享署名4.0
国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Jan 3,2021