VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > go语言 >
  • Golang之旅——Defer

defer#

首先来看一下官方的解析:

  1. A deferred function’s arguments are evaluated when the defer statement is evaluated.
  2. Deferred function calls are executed in Last In First Out order after the surrounding function returns.
  3. Deferred functions may read and assign to the returning function’s named return values.

总结:
规则一:延迟函数的参数在defer语句出现时就已经确定下来了
规则二:延迟函数执行按后进先出顺序执行,即先出现的defer最后执行
规则三:延迟函数可能操作主函数的具名返回值

数据结构#

源码包src/src/runtime/runtime2.go:_defer定义了defer的数据结构:

	started bool
	heap    bool
	// openDefer indicates that this _defer is for a frame with open-coded
	// defers. We have only one defer record for the entire frame (which may
	// currently have 0, 1, or more defers active).
	openDefer bool
	sp        uintptr // sp at time of defer
	pc        uintptr // pc at time of defer
	fn        func()  // can be nil for open-coded defers
	_panic    *_panic // panic that is running defer
	link      *_defer // next defer on G; can point to either heap or stack!

	// If openDefer is true, the fields below record values about the stack
	// frame and associated function that has the open-coded defer(s). sp
	// above will be the sp for the frame, and pc will be address of the
	// deferreturn call in the function.
	fd   unsafe.Pointer // funcdata for the function associated with the frame
	varp uintptr        // value of varp for the stack frame
	// framepc is the current pc associated with the stack frame. Together,
	// with sp above (which is the sp associated with the stack frame),
	// framepc/sp can be used as pc/sp pair to continue a stack trace via
	// gentraceback().
	framepc uintptr
  • sp 和 pc 分别代表栈指针和调用方的程序计数器;
  • fn 是 defer 关键字中传入的函数;
  • _panic 是触发延迟调用的结构体,可能为空;
  • openDefer 表示当前 defer 是否经过开放编码的优化;

我们知道defer后面一定要接一个函数的,所以defer的数据结构跟一般函数类似,也有栈地址、程序计数器、函数地址等等。
与函数不同的一点是它含有一个指针,可用于指向另一个defer,每个goroutine数据结构中实际上也有一个defer指针,该指针指向一个defer的单链表,每次声明一个defer时就将defer插入到单链表表头,每次执行defer时就从单链表表头取出一个defer执行。
下图展示多个defer被链接的过程:
image
新声明的defer总是添加到链表头部,函数返回前执行defer则是从链表首部依次取出执行。
一个goroutine可能连续调用多个函数,defer添加过程跟上述流程一致,进入函数时添加defer,离开函数时取出defer,所以即便调用多个函数,也总是能保证defer是按FIFO方式执行的。

defer的创建和执行#

源码包src/runtime/panic.go定义了两个方法分别用于创建defer和执行defer。

  • deferproc(): 在声明defer处调用,其将defer函数存入goroutine的链表中;
  • deferreturn():在return指令,准确的讲是在ret指令前调用,其将defer从goroutine链表中取出并执行。

可以简单这么理解,在编译在阶段,声明defer处插入了函数deferproc(),在函数return前插入了函数deferreturn()。

defer和return执行顺序#

return执行顺序#

先来看看return的执行顺序:
return不是原子操作,执行过程是: 保存返回值(若有)-->执行defer(若有)-->执行ret跳转。
即return是将i值存入栈中作为返回值,然后执行跳转。
那return又分为匿名返回值和有名返回值,这两种情况又有一些区别。

有名返回值#

package main

import "fmt"

func main() {
	fmt.Println(foo())
}

func foo() (ret int) {
	defer func() {
		ret++
	}()

	return 0
}

// 输出
// 1

首先,return在最后返回了0,注意了,这时候会执行第一步,**ret=0**
在完成赋值后,执行存在的defer,在defer中,我们又执行了ret++,所以ret的值变为了1
所以在最终 foo()函数返回值为1

匿名返回值#

package main

import "fmt"

func main() {
	fmt.Println(foo())
}

func foo() int {
	var i int

	defer func() {
		i++
	}()

	return 1
}

// 输出
// 1

上面的return语句,直接把1写入栈中作为返回值,延迟函数无法操作该返回值,所以就无法影响返回值。
所以结论是:第一步先return赋值,第二步再执行defer,第三步执行return返回。

defer与panic#

(这部分的内容单独放在错误处理中去描述)

总结#

  • defer定义的延迟函数参数在defer语句出时就已经确定下来了
  • defer定义顺序与实际执行顺序相反
  • return不是原子操作,执行过程是: 保存返回值(若有)-->执行defer(若有)-->执行ret跳转
  • 申请资源后立即使用defer关闭资源是好习惯

作者:chenchen4396

出处:https://www.cnblogs.com/chenchen4396/p/17629547.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。



相关教程