本文进行了关键字defer和内建函数panic、recover的介绍和使用细节。
defer
Go 语言的 defer
会在当前函数返回前执行传入的函数,它会经常被用于关闭文件描述符、关闭数据库连接以及解锁资源。
在文章Go-函数详解(参数、返回值、init函数、匿名函数、defer)中进行了简单的使用,但是还不够深入,于是在知乎提了问题,今天做下总结。
调用时机
代码
//------调用时机:所在函数结束或返回前------
func callTime() {
{
defer fmt.Println("defer in callTime()")
fmt.Println("code block finish..")
}
fmt.Println("callTime() finish...")
}
结果
code block finish…
callTime() finish…
defer in callTime()
调用时机在函数/方法结束或返回前
多次调用时的执行顺序
代码
func moreDefer() {
for i:=1;i<5;i++{
defer fmt.Println("defer",i)
}
fmt.Println("moreDefer() finish...")
}
结果
moreDefer() finish…
defer 4
defer 3
defer 2
defer 1
栈的顺序调用,先入后出。
传参问题
代码
func deferPara() {
i := 0
defer fmt.Println("defer",i,"in deferPara()")
i++
fmt.Println("deferPara finish...,i is ", i)
}
或许你认为结果是这样的:
deferPara finish…,i is 1
defer 1 in deferPara()
因为defer在函数结束前运行嘛,但事实上结果是这样的:
deferPara finish...,i is 1 defer 0 in deferPara()
defer会在到达所在行时,就将变量复制一份传过去。想到的解决方案如下:
- 引用类型就没有问题了
- 如果参数是值类型,你的defer不修改参数,你可以传地址。
- 如果参数是值类型,你也可以将defer放在函数/方法不修改参数后。
- 如果参数是值类型,你可以使用匿名函数,函数体内再用参数。
func paraFix() {
i := 0
defer fmt.Println("send addr:defer",&i,"in paraFix()")
defer func() {fmt.Println("no name func: defer",i,"in paraFix()")}()
i++
defer fmt.Println("put defer later:defer",i,"in deferPara()")
}
源代码
结构体
src->runtime->runtime2.go
type _defer struct {
siz int32 // 包含参数和结果
started bool // 是否开始
heap bool // 是否分配在堆上
openDefer bool // 是否开放编码
sp uintptr // 栈指针
pc uintptr // 程序计数器
fn *funcval // 开放编码时可为nil
_panic *_panic // 正在运行的defer的panic
link *_defer // _defer指针
fd unsafe.Pointer // 预分配的函数数据
varp uintptr
framepc uintptr
}
_defer是一个单链表(链栈),采用头插的方式,取的时候先取头的。
编译
src->cmd->compoile->internal->gc->ssa.go stmt方法的一个case
case ODEFER:
if Debug_defer > 0 {
var defertype string
if s.hasOpenDefers {
defertype = "open-coded"
} else if n.Esc == EscNever {
defertype = "stack-allocated"
} else {
defertype = "heap-allocated"
}
Warnl(n.Pos, "%s defer", defertype)
}
if s.hasOpenDefers {
s.openDeferRecord(n.Left)
} else {
d := callDefer
if n.Esc == EscNever {
d = callDeferStack
}
s.callResult(n.Left, d)
}
有些defer将在栈上分配,有些在堆上分配。首先是开放编码进行优化,其次是栈,最后是堆,分配到栈上可以节约内存分配带来的额外开销。
panic
执行defer
当panic异常发生时,程序会中断运行,并立即执行在该goroutine中被延迟的函数(defer机制)。随后,程序崩溃并输出日志信息。
func panicDefer() {
fmt.Println("code before panic")
defer fmt.Println("defer in panicDefer")
panic("something wrong in panic Defer")
fmt.Println("code after panic")
}
结果
code before panic
defer in panicDefer
panic: something wrong in panic Defergoroutine 1 [running]:
main.panicDefer()
E:/Workspace/Go_workspace/learn_go/src/defer_panic_recover/main/main.go:42 +0x10a
main.main()
E:/Workspace/Go_workspace/learn_go/src/defer_panic_recover/main/main.go:62 +0x27
exit status 2
利用defer就可以实现有panic时也能进行资源释放等。
跨协程问题
panic
只会触发当前 goroutine 的 defer
func panicGoroutine() {
defer println("defer in main")
go func() {
defer println("defer in goroutine")
panic("something wrong...")
}()
time.Sleep(time.Second)
}
结果:
defer in goroutine
panic: something wrong…goroutine 6 [running]:
main.panicGoroutine.func1()
E:/Workspace/Go_workspace/learn_go/src/defer_panic_recover/main/main.go:50 +0x78
created by main.panicGoroutine
E:/Workspace/Go_workspace/learn_go/src/defer_panic_recover/main/main.go:48 +0x78
exit status 2
协程外面的defer执行不了
源代码
结构体
src->runtime->runtime2.go
type _panic struct {
argp unsafe.Pointer // 指向defer栈的函数指针
arg interface{} // panic参数
link *_panic // 先前panic的指针
pc uintptr // 运行时,此panic被绕过时返回到哪
sp unsafe.Pointer // 运行时,此panic被绕过时返回到哪
recovered bool // 是否此panic结束
aborted bool // 这个panic被终止
goexit bool
}
崩溃
src->runtime->panic.go
一、网安学习成长路线图
网安所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
二、网安视频合集
观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
三、精品网安学习书籍
当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。
四、网络安全源码合集+工具包
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
五、网络安全面试题
最后就是大家最关心的网络安全面试题板块