Golang-defer的使用

原文链接:https://www.ahaoaha.top/2019/05/24/Golang-defer的使用/

在golang中defer关键字用来在函数return或者panic之前完成一些动作,经常用来释放资源等,defer的执行时机如下:

  1. 保存返回值
  2. 执行defer动作
  3. 执行空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执行
上一篇:区块链技术语言(二十六)——Go语言异常处理


下一篇:如何加速golang写业务的开发速度