defer实现原理
# 热身练习
下面程序输出什么?
func deferTest() { var a = 1 defer fmt.Println(a) a = 2 return }
1
2
3
4
5
6
71
: 延迟函数 fmt.Println(a) 的参数在 defer 语句出现的时候就已经确定下来了,所以不管后面如何修改 a 变量,都不会影响延迟函数。
下面程序输出什么?
package main import "fmt" func main() { deferTest() } func deferTest() { var arr = [3]int{1, 2, 3} defer printTest(&arr) arr[0] = 4 return } func printTest(array *[3]int) { for i := range array { fmt.Println(array[i]) } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
214 2 3
: 延迟函数 printTest() 的参数在 defer 语句出现的时候就已经确定下来了,即为数组的地址,延迟函数执行的时机是在 return 语句之前,所以对数组的最终修改的值会被打印出来。
下面程序输出什么?
package main import "fmt" func main() { res := deferTest() fmt.Println(res) } func deferTest () (result int) { i := 1 defer func() { result++ }() return i }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
182
: 函数的 return 语句并不是原子级的,实际的执行过程为为设置返回值—>ret,defer 语句是在返回前执行,所以返回过程是:「设置返回值—>执行defer—>ret」。所以 return 语句先把 result 设置成i
的值(1),defer 语句中又把 result 递增 1 ,所以最终返回值为 2 。
# defer实现原理
源码包 src/src/runtime/runtime2.go:_defer 定义了defer的数据结构:
type _defer struct { siz int32 // includes both arguments and results 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 *funcval // can be nil for open-coded defers _panic *_panic // panic that is running defer link *_defer // 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 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26sp
函数栈指针pc
程序计数器fn
函数地址link
指向自身结构的指针,用于链接多个 defer
defer 语句后面是要跟一个函数的,所以 defer 的数据结构跟一般的函数类似,不同之处是 defer 结构含有一个指针,用于指向另一个 defer ,每个 goroutine 数据结构中实际上也有一个 defer 指针指向一个 defer 的单链表,每次声明一个defer 时就将 defer 插入单链表的表头,每次执行 defer 时就从单链表的表头取出一个 defer 执行。保证 defer 是按 FIFO 方式执行的。
# defer的创建和执行
源码包 src/runtime/panic.go 中定义了两个方法分别用于创建defer和执行defer。
deferproc()
:在声明 defer 处调用,其将defer 函数存入 goroutine 的链表中;deferreturn()
:在 return 指令,准确的讲是在 ret 指令前调用,其将 defer 从 goroutine链表中取出并执行。
# 归纳总结
- defer 定义的延迟函数的参数在 defer 语句出时就已经确定下来了
- defer 定义顺序与实际执行顺序相反
- return 不是原子级操作的,执行过程是: 保存返回值—>执行 defer —>执行ret
上次更新: 2023/04/16, 18:35:33