上卷 程序设计
中卷 标准库
- 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.5 终止线程
线程自入口函数开始,进入调度循环,便不再返回。哪怕休眠被唤醒,也是回到调度循环内。
// proc.go // Create a new g running fn. func newproc(fn *funcval) { // g0 systemstack(func() { newg := newproc1(fn, gp, pc) _p_ := getg().m.p.ptr() runqput(_p_, newg, true) if mainStarted { wakep() } }) } func wakep() { startm(nil, true) }
// proc.go // mstart is the entry-point for new Ms. // It is written in assembly, uses ABI0, is marked TOPFRAME, and calls mstart0. func mstart() // mstart0 is the Go entry-point for new Ms. func mstart0() { mstart1() mexit(osStack) } func mstart1() { // g0 _g_ := getg() if _g_ != _g_.m.g0 { throw("bad runtime·mstart") } // 初始化 g0.sched。 _g_.sched.g = guintptr(unsafe.Pointer(_g_)) _g_.sched.pc = getcallerpc() _g_.sched.sp = getcallersp() schedule() }
入口函数初始化 g0.sched 设置,寄存器值指向 mstart0
。也就是说,内核函数执行时,第一帧是 mstart0。相关函数不会修改 g0.sched 属性,总是自第二帧开始复用。
调度函数 schedule 找到任务 G 后,以 execute 调用 gogo(G.sched)
切换用户栈,执行用户函数。该函数执行结束,其尾部 RET
指令转入 goexit,再次切换回 g0,回到调度函数。
只有 “出错” 时,才会执行 gogo(g0.sched)
。此时 gogo jmp g0.sched.pc
,跳转回 mstart0,执行 mexit
终止线程。
// Finishes execution of the current goroutine. func goexit1() { mcall(goexit0) } // goexit continuation on g0. func goexit0(gp *g) { ... locked := gp.lockedm != 0 if locked { // The goroutine may have locked this thread because // it put it in an unusual kernel state. Kill it // rather than returning it to the thread pool. // Return to mstart, which will release the P and exit // the thread. 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() }
终止前,从 allm 移除,并释放相关资源。
// mexit tears down and exits the current thread. // // Don't call this directly to exit the thread, since it must run at // the top of the thread stack. Instead, use gogo(&_g_.m.g0.sched) to // unwind the stack to the point that exits the thread. func mexit(osStack bool) { g := getg() m := g.m // Free the gsignal stack... // Remove m from allm. lock(&sched.lock) for pprev := &allm; *pprev != nil; pprev = &(*pprev).alllink { if *pprev == m { *pprev = m.alllink goto found } } throw("m not found in allm") found: // 加入 sched.freem 链表。 if !osStack { atomic.Store(&m.freeWait, 1) m.freelink = sched.freem sched.freem = m } // 释放 P 和相关资源。 handoffp(releasep()) mdestroy(m) if osStack { // Return from mstart and let the system thread // library free the g0 stack and terminate the thread. return } // mstart is the thread's entry point, so there's nothing to // return to. Exit the thread directly. exitThread will clear // m.freeWait when it's done with the stack and the m can be // reaped. exitThread(&m.freeWait) }
// sys_linux_amd64.s // func exitThread(wait *uint32) TEXT runtime·exitThread(SB),NOSPLIT,$0-8 MOVQ wait+0(FP), AX // We're done using the stack. MOVL $0, (AX) MOVL $0, DI // exit code MOVL $SYS_exit, AX SYSCALL // We may not even have a stack any more. INT $3 JMP 0(PC)
至于 sched.freem 链表,在 allocm 里被清理,释放所持有的 g0 内存。
// proc.go // Allocate a new m unassociated with any thread. func allocm(_p_ *p, fn func(), id int64) *m { // Release the free M list. We need to do this somewhere and // this may free up a stack we can use. if sched.freem != nil { lock(&sched.lock) var newList *m for freem := sched.freem; freem != nil; { if freem.freeWait != 0 { next := freem.freelink freem.freelink = newList newList = freem freem = next continue } // stackfree must be on the system stack, but allocm is // reachable off the system stack transitively from // startm. systemstack(func() { stackfree(freem.g0.stack) }) freem = freem.freelink } sched.freem = newList unlock(&sched.lock) } }
测试
结论:正常情况,线程不退出,进入休眠队列。当前只有 UnlockOSThread 出错,才导致终止。
// test/main.go package main import ( "runtime" "time" ) func test() { runtime.LockOSThread() // defer runtime.UnlockOSThread() time.Sleep(time.Second * 3) } func main() { for i := 0; i < 100; i++ { go test() } time.Sleep(time.Second * 5) }
按是否调用 UnlockOSThread 查看输出结果。线程数量很好地说明线程休眠,或是被终止。
$ go build && GODEBUG=schedtrace=500 ./test ; 解锁。 SCHED 0ms: threads=4 idlethreads=1 SCHED 504ms: threads=104 idlethreads=1 SCHED 1004ms: threads=104 idlethreads=1 SCHED 1506ms: threads=104 idlethreads=1 SCHED 2007ms: threads=104 idlethreads=1 SCHED 2515ms: threads=104 idlethreads=1 SCHED 3023ms: threads=104 idlethreads=101 SCHED 3526ms: threads=104 idlethreads=101 SCHED 4033ms: threads=104 idlethreads=101 SCHED 4544ms: threads=104 idlethreads=101 $ go build && GODEBUG=schedtrace=500 ./test ; 未解锁。 SCHED 0ms: threads=7 idlethreads=0 SCHED 504ms: threads=103 idlethreads=0 SCHED 1013ms: threads=103 idlethreads=0 SCHED 1515ms: threads=103 idlethreads=0 SCHED 2015ms: threads=103 idlethreads=0 SCHED 2527ms: threads=103 idlethreads=0 SCHED 3032ms: threads=7 idlethreads=4 SCHED 3538ms: threads=7 idlethreads=4 SCHED 4049ms: threads=7 idlethreads=4 SCHED 4561ms: threads=7 idlethreads=4
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论