go语言调度gmp原理(5)
线程管理
go语言的运行时会通过调度器改变线程的所有权,它也提供了runtime.lockOSthread和runtime.UnlockOSthread,让我们能绑定goroutine和线程完成一些比较特殊的操作。goroutine应该在调用操作系统服务或者依赖线程状态的非go语言库时调用runtime.lockOSThread函数,例如C语言图形库等
runtime.lockOSThread会通过如下所示的代码绑定goroutine和当前线程
func lockOSThread() {
getg().m.lockedInt++
dolockOSThread()
}
func dolockOSThread() {
if GOARCH == "wasm" {
return // no threads on wasm yet
}
gp := getg()
gp.m.lockedg.set(gp)
gp.lockedm.set(gp.m)
}
runtime.dolockOSThread会分别设置线程的lockedg字段和goroutine的lockedm字段,这两行代码会绑定线程和goroutine
当goroutine完成特定操作之后,会调用函数runtime.UnlockOSThread分离goroutine和线程
func unlockOSThread() {
gp := getg()
if gp.m.lockedInt == 0 {
systemstack(badunlockosthread)
}
gp.m.lockedInt--
dounlockOSThread()
}
func dounlockOSThread() {
if GOARCH == "wasm" {
return // no threads on wasm yet
}
gp := getg()
if gp.m.lockedInt != 0 || gp.m.lockedExt != 0 {
return
}
gp.m.lockedg = 0
gp.lockedm = 0
}
函数执行的过程与runtime.LockOSThread正好相反。在多数服务之中,我们用不到这对函数
线程的生命周期
go语言的运行时会通过runtime.startm启动线程来执行处理器P,如果我们在该函数中没能从空闲列表中获取线程M,就会调用runtime.newm创建新线程
func newm(fn func(), pp *p, id int64) {
// allocm adds a new M to allm, but they do not start until created by
// the OS in newm1 or the template thread.
//
// doAllThreadsSyscall requires that every M in allm will eventually
// start and be signal-able, even with a STW.
//
// Disable preemption here until we start the thread to ensure that
// newm is not preempted between allocm and starting the new thread,
// ensuring that anything added to allm is guaranteed to eventually
// start.
acquirem()
mp := allocm(pp, fn, id)
mp.nextp.set(pp)
mp.sigmask = initSigmask
if gp := getg(); gp != nil && gp.m != nil && (gp.m.lockedExt != 0 || gp.m.incgo) && GOOS != "plan9" {
// We're on a locked M or a thread that may have been
// started by C. The kernel state of this thread may
// be strange (the user may have locked it for that
// purpose). We don't want to clone that into another
// thread. Instead, ask a known-good thread to create
// the thread for us.
//
// This is disabled on Plan 9. See golang.org/issue/22227.
//
// TODO: This may be unnecessary on Windows, which
// doesn't model thread creation off fork.
lock(&newmHandoff.lock)
if newmHandoff.haveTemplateThread == 0 {
throw("on a locked thread with no template thread")
}
mp.schedlink = newmHandoff.newm
newmHandoff.newm.set(mp)
if newmHandoff.waiting {
newmHandoff.waiting = false
notewakeup(&newmHandoff.wake)
}
unlock(&newmHandoff.lock)
// The M has not started yet, but the template thread does not
// participate in STW, so it will always process queued Ms and
// it is safe to releasem.
releasem(getg().m)
return
}
newm1(mp)
releasem(getg().m)
}
func newm1(mp *m) {
if iscgo {
var ts cgothreadstart
if _cgo_thread_start == nil {
throw("_cgo_thread_start missing")
}
ts.g.set(mp.g0)
ts.tls = (*uint64)(unsafe.Pointer(&mp.tls[0]))
ts.fn = unsafe.Pointer(abi.FuncPCABI0(mstart))
if msanenabled {
msanwrite(unsafe.Pointer(&ts), unsafe.Sizeof(ts))
}
if asanenabled {
asanwrite(unsafe.Pointer(&ts), unsafe.Sizeof(ts))
}
execLock.rlock() // Prevent process clone.
asmcgocall(_cgo_thread_start, unsafe.Pointer(&ts))
execLock.runlock()
return
}
execLock.rlock() // Prevent process clone.
newosproc(mp)
execLock.runlock()
}
创建新线程需要使用如下所示的runtime.newosproc,该函数在Linux平台上会通过系统调用clone创建新的操作系统线程,它也是创建线程链路上距离操作系统最近的go语言函数
func newosproc(mp *m) {
// We pass 0 for the stack size to use the default for this binary.
thandle := stdcall6(_CreateThread, 0, 0,
abi.FuncPCABI0(tstart_stdcall), uintptr(unsafe.Pointer(mp)),
0, 0)
if thandle == 0 {
if atomic.Load(&exiting) != 0 {
// CreateThread may fail if called
// concurrently with ExitProcess. If this
// happens, just freeze this thread and let
// the process exit. See issue #18253.
lock(&deadlock)
lock(&deadlock)
}
print("runtime: failed to create new OS thread (have ", mcount(), " already; errno=", getlasterror(), ")\n")
throw("runtime.newosproc")
}
// Close thandle to avoid leaking the thread object if it exits.
stdcall1(_CloseHandle, thandle)
}
使用系统调用clone创建的线程会在线程主动调用exit或者传入的函数runtime.mstart返回时主动退出,runtime.mstart会执行调用runtime.newm时传入的匿名函数fn,至此也就完成了从线程创建到销毁的整个闭环
小结
调度器主要函数运行逻辑
标签:runtime,thread,gp,调度,ts,线程,mp,go,gmp From: https://www.cnblogs.com/zpf253/p/17426798.html