原文链接:https://www.ahaoaha.top/2019/05/24/Golang-defer的使用/
在golang中defer
关键字用来在函数return或者panic之前完成一些动作,经常用来释放资源等,defer
的执行时机如下:
- 保存返回值
- 执行defer动作
- 执行空return
defer执行的顺序
如果一个函数中有多个defer动作,那么这些defer动作的执行顺序类似于栈,先被注册则最后被执行
package main
import "fmt"
func main() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
}
//执行的结果为
//3
//2
//1
defer的用途一:回收资源
虽然golang是一款有GC的语言,但是在特定的情况下,某些资源还是需要程序员手动释放,类似互斥锁的关闭,chan的关闭,套接字的关闭等,defer可以在函数退出的时候来释放这些资源。
defer的用途二:捕获panic
如果代码中发生panic,则发生panic之后的代码就不会再被执行,但defer中的动作会被执行,所以通常可以在defer中对代码中的panic进行recover,使程序不会因为panic而异常退出,recover只有在defer中才有意义,如果在defer之外,发生panic之后,recover就不会被执行到了。
defer函数的参数
defer函数的参数会在声明defer的时被确定,即使在defer之后参数被修改也不会影响defer原本的参数内容。
defer的执行方式
golang编译器在编译的时候,会将defer转换为相应的函数调用,在声明defer的CALL runtime.deferproc
,然后再函数返回之前CALL runtime.deferreturn`
- runtime.deferproc
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
//getg函数用来获取当前正在执行的goroutine信息
if getg().m.curg != getg() {
// go code on the system stack can't defer
throw("defer on system stack")
}
// the arguments of fn are in a perilous state. The stack map
// for deferproc does not describe them. So we can't let garbage
// collection or stack copying trigger until we've copied them out
// to somewhere safe. The memmove below does that.
// Until the copy completes, we can only call nosplit routines.
sp := getcallersp()
argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
callerpc := getcallerpc()
d := newdefer(siz)
if d._panic != nil {
throw("deferproc: d.panic != nil after newdefer")
}
d.fn = fn
d.pc = callerpc
d.sp = sp
switch siz {
case 0:
// Do nothing.
case sys.PtrSize:
*(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))
default:
memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))
}
// deferproc returns 0 normally.
// a deferred func that stops a panic
// makes the deferproc return 1.
// the code the compiler generates always
// checks the return value and jumps to the
// end of the function if deferproc returns != 0.
return0()
// No code can go here - the C return register has
// been set and must not be clobbered.
}
- runtime.deferreturn
//这里的参数arg0就是defer后面对应的要调用的函数
func deferreturn(arg0 uintptr) {
gp := getg()
d := gp._defer
if d == nil {
return
}
sp := getcallersp()
if d.sp != sp {
return
}
// Moving arguments around.
//
// Everything called after this point must be recursively
// nosplit because the garbage collector won't know the form
// of the arguments until the jmpdefer can flip the PC over to
// fn.
switch d.siz {
case 0:
// Do nothing.
case sys.PtrSize:
*(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
default:
memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))
}
fn := d.fn
d.fn = nil
gp._defer = d.link
freedefer(d)
jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
}
- _defer
// A _defer holds an entry on the list of deferred calls.
// If you add a field here, add code to clear it in freedefer.
type _defer struct {
siz int32
started bool
sp uintptr // sp函数堆栈指针
pc uintptr // 程序计数器
fn *funcval // 函数地址
_panic *_panic // panic that is running defer
link *_defer //指向自身结构体,用来链接多个defer
}
- 在同一个函数中,每次声明一个defer,就会向link的单链表中头插一个_defer结构体,当要执行defer时,也从单链表的头部拿出一个defer执行