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