golang中goexit的使用方法介绍
这篇文章主要介绍了golang中goexit的使用方法介绍,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。
在学员群里,有同学在用 dlv 调试时看到了令人不解的 goexit:goexit 函数是啥,为啥 go fun(){}() 的上层是它?看着像是一个“退出”函数,为什么会出现在最上层?
其实如果看过 pprof 的火焰图,也会经常看到 goexit 这个函数。
我们来个例子重现一下:
packagemainimport"time"funcmain(){gofunc(){println("helloworld")}()time.Sleep(10*time.Minute)}
启动 dlv 调试,并分别在不同的地方打上断点:
(dlv)ba.go:5Breakpoint1(enabled)setat0x106d12fformain.main()./a.go:5(dlv)ba.go:6Breakpoint2(enabled)setat0x106d13dformain.main()./a.go:6(dlv)ba.go:7Breakpoint3(enabled)setat0x106d1a0formain.main.func1()./a.go:7
执行命令 c 运行到断点处,再执行 bt 命令得到 main 函数的调用栈:
(dlv)bt00x000000000106d12finmain.mainat./a.go:510x0000000001035c0finruntime.mainat/usr/local/go/src/runtime/proc.go:20420x0000000001064961inruntime.goexitat/usr/local/go/src/runtime/asm_amd64.s:1374
它的上一层是 runtime.main,找到原代码位置,位于 src/runtime/proc.go 里的 main 函数,它是 Go 进程的 main goroutine,这里会执行一些 init 操作、开启 GC、执行用户 main 函数……
fn:=main_main//proc.go:203fn()//proc.go:204
其中 fn 是 main_main 函数,表示用户的 main 函数,执行到了这里,才真正将权力交给用户。
继续执行 c 命令和 bt 命令,得到 go 这一行的调用栈:
00x000000000106d13dinmain.mainat./a.go:610x0000000001035c0finruntime.mainat/usr/local/go/src/runtime/proc.go:20420x0000000001064961inruntime.goexitat/usr/local/go/src/runtime/asm_amd64.s:1374
以及 println 这一句的调用栈:
00x000000000106d1a0inmain.main.func1at./a.go:710x0000000001064961inruntime.goexitat/usr/local/go/src/runtime/asm_amd64.s:1374
可以看到,调用栈的最上层都是 runtime.goexit,我们跟着注明了的代码行数,顺藤摸瓜,找到 goexit 代码:
//Thetop-mostfunctionrunningonagoroutine//returnstogoexit+PCQuantum.TEXTruntime·goexit(SB),NOSPLIT,$0-0BYTE$0x90//NOPCALLruntime·goexit1(SB)//doesnotreturn//tracebackfromgoexit1musthitcoderangeofgoexitBYTE$0x90//NOP
这还是个汇编函数,它接着调用 goexit1 函数、goexit0 函数,主要的功能就是将 goroutine 的各个字段清零,放入 gFree 队列里,等待将来进行复用。
另一方面,goexit 函数的地址是在创建 goroutine 的过程中,塞到栈上的。让 CPU “误以为”:func() 是由 goexit 函数调用的。这样一来,当 func() 执行完毕时,会返回到 goexit 函数做一些清理工作。
下面这张图能看出在 newg 的栈底塞了一个 goexit 函数的地址:
goexit 返回地址
对应的路径是:
newporc->newporc1->gostartcallfn->gostartcall
来看 newproc1 中的关键几行代码:
newg.sched.pc=funcPC(goexit)+sys.PCQuantumnewg.sched.g=guintptr(unsafe.Pointer(newg))gostartcallfn(&newg.sched,fn)
这里的 newg 就是创建的 goroutine,每个新建的 goroutine 都会执行这些代码。而 sched 结构体其实保存的是 goroutine 的执行现场,每当 goroutine 被调离 CPU,它的执行进度就是保存到这里。进度主要就是 SP、BP、PC,分别表示栈顶地址、栈底地址、指令位置,等 goroutine 再次得到 CPU 的执行权时,会把 SP、BP、PC 加载到寄存器中,从而从断点处恢复运行。
回到上面的几行代码,pc 被赋值成了 funcPC(goexit),最后在 gostartcall 里:
//adjustGobufasifitexecutedacalltofnwithcontextctxt//andthendidanimmediategosave.funcgostartcall(buf*gobuf,fn,ctxtunsafe.Pointer){sp:=buf.sp...sp-=sys.PtrSize*(*uintptr)(unsafe.Pointer(sp))=buf.pcbuf.sp=spbuf.pc=uintptr(fn)buf.ctxt=ctxt}
sp 其实就是栈顶,第 7 行代码把 buf.pc,也就是 goexit 的地址,放在了栈顶的地方,熟悉 Go 函数调用规约的朋友知道,这个位置其实就是 return addr,将来等 func() 执行完,就会回到父函数继续执行,这里的父函数其实就是 goexit。
一切早已注定。
不过注意一点,main goroutine 和普通的 goroutine 不同的是,前者执行完用户 main 函数后,会直接执行 exit 调用,整个进程退出:
exit
也就不会进入 goexit 函数。而普通 goroutine 执行完毕后,则直接进入 goexit 函数,做一些清理工作。
这也就是为什么只要 main goroutine 执行完了,就不会等其他 goroutine,直接退出。一切都是因为 exit 这个调用。
今天我们主要讲了 goexit 是怎么被安插到 goroutine 的栈上,从而实现 goroutine 执行完毕后再回到 goexit 函数。
以上就是golang中goexit的使用方法介绍的详细内容了,看完之后是否有所收获呢?如果想了解更多相关内容,欢迎来恰卡编程网行业资讯!
推荐阅读
-
go如何实现职责链模式
-
Go Callvis如何使用
-
go pprof如何使用
-
Go框架三件套Gorm、Kitex、Hertz怎么使用
-
Go语言单元测试和基准测试实例代码分析
-
go Realize怎么使用
goRealize怎么使用本文小编为大家详细介绍“goReal...
-
14天学会Go语言第四天:Array Map Slice 数组 和切片
-
如何从 PHP 过渡到 Golang?
我是PHP开发者,转Go两个月了吧,记录一下使用Golang怎么一步步开发新项目。本着有坑填坑,有错改错的宗...
-
零经验Go语言MVC框架,并推荐PHP函数库和MySQL表自动转struct
最近搭建k8s系统,为了调试自动部署用Go做了个MVC框架,这是个适用于喜欢Go语言练习的框架,零Go语言经验学习Go;配合Go模...
-
从php到Golang系统的演变