1. defer的使用   defer 延迟调用。我们先来看一下,有defer关键字的代码执行顺序: 复制代码 1 func main() { 2 defer func() { 3 fmt.Println("1号输出") 4 }() 5 defer func() { 6 fmt.Println("2号输出") 7 }() 8 } 复制代码   输出结果: 1 2号出来 2 1号出来   结论:多个defer的执行顺序是倒序执行(同入栈先进后出)。   由例子可以看出来,defer有延迟生效的作用,先使用defer的语句延迟到最后执行。 1.1 defer与返回值之间的顺序 复制代码 1 func defertest() int 2 3 func main() { 4 fmt.Println("main:", defertest()) 5 } 6 7 func defertest() int { 8 var i int 9 defer func() { 10 i++ 11 fmt.Println("defer2的值:", i) 12 }() 13 defer func() { 14 i++ 15 fmt.Println("defer1的值:", i) 16 }() 17 return i 18 } 复制代码   输出结果: 复制代码 1 defer1的值: 1 2 defer2的值: 2 3 main: 0 复制代码   结论:return最先执行->return负责将结果写入返回值中->接着defer开始执行一些收尾工作->最后函数携带当前返回值退出   return的时候已经先将返回值给定义下来了,就是0,由于i是在函数内部声明所以即使在defer中进行了++操作,也不会影响return的时候做的决定。 复制代码 1 func test() (i int) 2 3 func main() { 4 fmt.Println("main:", test()) 5 } 6 7 func test() (i int) { 8 defer func() { 9 i++ 10 fmt.Println("defer2的值:", i) 11 }() 12 defer func() { 13 i++ 14 fmt.Println("defer1的值:", i) 15 }() 16 return i 17 } 复制代码   详解:由于返回值提前声明了,所以在return的时候决定的返回值还是0,但是后面两个defer执行后进行了两次++,将i的值变为2,待defer执行完后,函数将i值进行了返回。 2. defer定义和执行 复制代码 1 func test(i *int) int { 2 return *i 3 } 4 5 func main(){ 6 var i = 1 7 8 // defer定义的时候test(&i)的值就已经定了,是1,后面就不会变了 9 defer fmt.Println("i1 =" , test(&i)) 10 i++ 11 12 // defer定义的时候test(&i)的值就已经定了,是2,后面就不会变了 13 defer fmt.Println("i2 =" , test(&i)) 14 15 // defer定义的时候,i就已经确定了是一个指针类型,地址上的值变了,这里跟着变 16 defer func(i *int) { 17 fmt.Println("i3 =" , *i) 18 }(&i) 19 20 // defer定义的时候i的值就已经定了,是2,后面就不会变了 21 defer func(i int) { 22 //defer 在定义的时候就定了 23 fmt.Println("i4 =" , i) 24 }(i) 25 26 defer func() { 27 // 地址,所以后续跟着变 28 var c = &i 29 fmt.Println("i5 =" , *c) 30 }() 31 32 // 执行了 i=11 后才调用,此时i值已是11 33 defer func() { 34 fmt.Println("i6 =" , i) 35 }() 36 37 i = 11 38 } 复制代码   结论:会先将defer后函数的参数部分的值(或者地址)给先下来【你可以理解为()里头的会先确定】,后面函数执行完,才会执行defer后函数的{}中的逻辑。 例题分析 复制代码 1 //例子1 2 func f() (result int) { 3 defer func() { 4 result++ 5 }() 6 return 0 7 } 8 //例子2 9 func f() (r int) { 10 t := 5 11 defer func() { 12 t = t + 5 13 }() 14 return t 15 } 16 //例子3 17 func f() (r int) { 18 defer func(r int) { 19 r = r + 5 20 }(r) 21 return 1 22 } 复制代码   例1的正确答案不是0,例2的正确答案不是10,例3的正确答案不是6......   这里先说一下返回值。defer是在return之前执行的。这条规则毋庸置疑,但最重要的一点是要明白,return xxx这一条语句并不是一条原子指令!   函数返回的过程:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。defer表达式可能会在设置函数返回值之后,且在返回到调用函数之前去修改返回值,使最终的函数返回值与你想象的不一致。   return xxx 可被改写成: 复制代码 1 返回值 = xxx 2 调用defer函数 3 空的return 复制代码   所以例子也可以改写成: 复制代码 1 //例1 2 func f() (result int) { 3 result = 0 //return语句不是一条原子调用,return xxx其实是赋值+ret指令 4 func() { //defer被插入到return之前执行,也就是赋返回值和ret指令之间 5 result++ 6 }() 7 return 8 } 9 //例2 10 func f() (r int) { 11 t := 5 12 r = t //赋值指令 13 func() { //defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过 14 t = t + 5 15 } 16 return //空的return指令 17 } 18 例3 19 func f() (r int) { 20 r = 1 //给返回值赋值 21 func(r int) { //这里改的r是传值传进去的r,不会改变要返回的那个r值 22 r = r + 5 23 }(r) 24 return //空的return 25 } 复制代码   所以例1的结果是1,例2的结果是5,例3的结果是1. 3. defer内部原理   从例子开始看: 复制代码 1 packmage main 2 3 import() 4 5 func main() { 6 defer println("这是一个测试") 7 } 复制代码   反编译一下看看: 复制代码 1 ➜ src $ go build -o test test.go 2 ➜ src $ go tool objdump -s "main\.main" test 复制代码 1 TEXT main.main(SB) /Users/tushanshan/go/src/test3.go 2 test3.go:5 0x104ea70 65488b0c2530000000 MOVQ GS:0x30, CX 3 test3.go:5 0x104ea79 483b6110 CMPQ 0x10(CX), SP 4 test3.go:5 0x104ea7d 765f JBE 0x104eade 5 test3.go:5 0x104ea7f 4883ec28 SUBQ $0x28, SP 6 test3.go:5 0x104ea83 48896c2420 MOVQ BP, 0x20(SP) 7 test3.go:5 0x104ea88 488d6c2420 LEAQ 0x20(SP), BP 8 test3.go:6 0x104ea8d c7042410000000 MOVL $0x10, 0(SP) 9 test3.go:6 0x104ea94 488d05e5290200 LEAQ go.func.*+57(SB), AX 10 test3.go:6 0x104ea9b 4889442408 MOVQ AX, 0x8(SP) 11 test3.go:6 0x104eaa0 488d05e6e50100 LEAQ go.string.*+173(SB), AX 12 test3.go:6 0x104eaa7 4889442410 MOVQ AX, 0x10(SP) 13 test3.go:6 0x104eaac 48c744241804000000 MOVQ $0x4, 0x18(SP) 14 test3.go:6 0x104eab5 e8b631fdff CALL runtime.deferproc(SB) 15 test3.go:6 0x104eaba 85c0 TESTL AX, AX 16 test3.go:6 0x104eabc 7510 JNE 0x104eace 17 test3.go:7 0x104eabe 90 NOPL 18 test3.go:7 0x104eabf e83c3afdff CALL runtime.deferreturn(SB) 19 test3.go:7 0x104eac4 488b6c2420 MOVQ 0x20(SP), BP 20 test3.go:7 0x104eac9 4883c428 ADDQ $0x28, SP 21 test3.go:7 0x104eacd c3 RET 22 test3.go:6 0x104eace 90 NOPL 23 test3.go:6 0x104eacf e82c3afdff CALL runtime.deferreturn(SB) 24 test3.go:6 0x104ead4 488b6c2420 MOVQ 0x20(SP), BP 25 test3.go:6 0x104ead9 4883c428 ADDQ $0x28, SP 26 test3.go:6 0x104eadd c3 RET 27 test3.go:5 0x104eade e8cd84ffff CALL runtime.morestack_noctxt(SB) 28 test3.go:5 0x104eae3 eb8b JMP main.main(SB) 29 :-1 0x104eae5 cc INT $0x3 30 :-1 0x104eae6 cc INT $0x3 31 :-1 0x104eae7 cc INT $0x3 复制代码 复制代码   编译器将defer处理成两个函数调用 deferproc 定义一个延迟调用对象,然后在函数结束前通过 deferreturn 完成最终调用。在defer出现的地方,插入了指令call runtime.deferproc,然后在函数返回之前的地方,插入指令call runtime.deferreturn。 内部结构 复制代码 1 //defer 2 type _defer struct { 3 siz int32 // 参数的大小 4 started bool // 是否执行过了 5 sp uintptr // sp at time of defer 6 pc uintptr 7 fn *funcval 8 _panic *_panic // defer中的panic 9 link *_defer // defer链表,函数执行流程中的defer,会通过 link这个 属性进行串联 10 } 11 //panic 12 type _panic struct { 13 argp unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink 14 arg interface{} // argument to panic 15 link *_panic // link to earlier panic 16 recovered bool // whether this panic is over 17 aborted bool // the panic was aborted 18 } 19 //g 20 type g struct { 21 _panic *_panic // panic组成的链表 22 _defer *_defer // defer组成的先进后出的链表,同栈 23 } 复制代码   因为 defer panic 都是绑定在运行的g上的,这里也说一下g中与 defer panic相关的属性   再把defer, panic, recover放一起看一下: 复制代码 1 func main() { 2 defer func() { 3 recover() 4 }() 5 panic("error") 6 } 复制代码   反编译结果: 复制代码 1 go build -gcflags=all="-N -l" main.go 2 go tool objdump -s "main.main" main 复制代码 1 go tool objdump -s "main\.main" main | grep CALL 2 main.go:4 0x4548d0 e81b00fdff CALL runtime.deferproc(SB) 3 main.go:7 0x4548f2 e8b90cfdff CALL runtime.gopanic(SB) 4 main.go:4 0x4548fa e88108fdff CALL runtime.deferreturn(SB) 5 main.go:3 0x454909 e85282ffff CALL runtime.morestack_noctxt(SB) 6 main.go:5 0x4549a6 e8d511fdff CALL runtime.gorecover(SB) 7 main.go:4 0x4549b5 e8a681ffff CALL runtime.morestack_noctxt(SB) 复制代码 复制代码   defer 关键字首先会调用 runtime.deferproc 定义一个延迟调用对象,然后再函数结束前,调用 runtime.deferreturn 来完成 defer 定义的函数的调用   panic 函数就会调用 runtime.gopanic 来实现相关的逻辑   recover 则调用 runtime.gorecover 来实现 recover 的功能 deferproc   根据 defer 关键字后面定义的函数 fn 以及 参数的size,来创建一个延迟执行的 函数,并将这个延迟函数,挂在到当前g的 _defer 的链表上,下面是deferproc的实现: 复制代码 1 func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn 2 sp := getcallersp() 3 argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn) 4 callerpc := getcallerpc() 5 // 获取一个_defer对象, 并放入g._defer链表的头部 6 d := newdefer(siz) 7 // 设置defer的fn pc sp等,后面调用 8 d.fn = fn 9 d.pc = callerpc 10 d.sp = sp 11 switch siz { 12 case 0: 13 // Do nothing. 14 case sys.PtrSize: 15 // _defer 后面的内存 存储 argp的地址信息 16 *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp)) 17 default: 18 // 如果不是指针类型的参数,把参数拷贝到 _defer 的后面的内存空间 19 memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz)) 20 } 21 return0() 22 } 复制代码   通过newproc 获取一个 _defer 的对象,并加入到当前g的 _defer 链表的头部,然后再把参数或参数的指针拷贝到 获取到的 _defer对象的后面的内存空间。   再看看newdefer 的实现: 复制代码 1 func newdefer(siz int32) *_defer { 2 var d *_defer 3 // 根据 size 通过deferclass判断应该分配的 sizeclass,就类似于 内存分配预先确定好几个sizeclass,然后根据size确定sizeclass,找对应的缓存的内存块 4 sc := deferclass(uintptr(siz)) 5 gp := getg() 6 // 如果sizeclass在既定的sizeclass范围内,去g绑定的p上找 7 if sc < uintptr(len(p{}.deferpool)) { 8 pp := gp.m.p.ptr() 9 if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil { 10 // 当前sizeclass的缓存数量==0,且不为nil,从sched上获取一批缓存 11 systemstack(func() { 12 lock(&sched.deferlock) 13 for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil { 14 d := sched.deferpool[sc] 15 sched.deferpool[sc] = d.link 16 d.link = nil 17 pp.deferpool[sc] = append(pp.deferpool[sc], d) 18 } 19 unlock(&sched.deferlock) 20 }) 21 } 22 // 如果从sched获取之后,sizeclass对应的缓存不为空,分配 23 if n := len(pp.deferpool[sc]); n > 0 { 24 d = pp.deferpool[sc][n-1] 25 pp.deferpool[sc][n-1] = nil 26 pp.deferpool[sc] = pp.deferpool[sc][:n-1] 27 } 28 } 29 // p和sched都没有找到 或者 没有对应的sizeclass,直接分配 30 if d == nil { 31 // Allocate new defer+args. 32 systemstack(func() { 33 total := roundupsize(totaldefersize(uintptr(siz))) 34 d = (*_defer)(mallocgc(total, deferType, true)) 35 }) 36 } 37 d.siz = siz 38 // 插入到g._defer的链表头 39 d.link = gp._defer 40 gp._defer = d 41 return d 42 } 复制代码   newdefer的作用是获取一个_defer对象, 并推入 g._defer链表的头部。根据size获取sizeclass,对sizeclass进行分类缓存,这是内存分配时的思想,先去p上分配,然后批量从全局 sched上获取到本地缓存,这种二级缓存的思想真的在go源码的各个部分都有。 deferreturn 复制代码 1 func deferreturn(arg0 uintptr) { 2 gp := getg() 3 // 获取g defer链表的第一个defer,也是最后一个声明的defer 4 d := gp._defer 5 // 没有defer,就不需要干什么事了 6 if d == nil { 7 return 8 } 9 sp := getcallersp() 10 // 如果defer的sp与callersp不匹配,说明defer不对应,有可能是调用了其他栈帧的延迟函数 11 if d.sp != sp { 12 return 13 } 14 // 根据d.siz,把原先存储的参数信息获取并存储到arg0里面 15 switch d.siz { 16 case 0: 17 // Do nothing. 18 case sys.PtrSize: 19 *(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d)) 20 default: 21 memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz)) 22 } 23 fn := d.fn 24 d.fn = nil 25 // defer用过了就释放了, 26 gp._defer = d.link 27 freedefer(d) 28 // 跳转到执行defer 29 jmpdefer(fn, uintptr(unsafe.Pointer(&arg0))) 30 } 复制代码 freedefer   释放defer用到的函数,应该跟调度器、内存分配的思想是一样的。 复制代码 1 func freedefer(d *_defer) { 2 // 判断defer的sizeclass 3 sc := deferclass(uintptr(d.siz)) 4 // 超出既定的sizeclass范围的话,就是直接分配的内存,那就不管了 5 if sc >= uintptr(len(p{}.deferpool)) { 6 return 7 } 8 pp := getg().m.p.ptr() 9 // p本地sizeclass对应的缓冲区满了,批量转移一半到全局sched 10 if len(pp.deferpool[sc]) == cap(pp.deferpool[sc]) { 11 // 使用g0来转移 12 systemstack(func() { 13 var first, last *_defer 14 for len(pp.deferpool[sc]) > cap(pp.deferpool[sc])/2 { 15 n := len(pp.deferpool[sc]) 16 d := pp.deferpool[sc][n-1] 17 pp.deferpool[sc][n-1] = nil 18 pp.deferpool[sc] = pp.deferpool[sc][:n-1] 19 // 先将需要转移的那批defer对象串成一个链表 20 if first == nil { 21 first = d 22 } else { 23 last.link = d 24 } 25 last = d 26 } 27 lock(&sched.deferlock) 28 // 把这个链表放到sched.deferpool对应sizeclass的链表头 29 last.link = sched.deferpool[sc] 30 sched.deferpool[sc] = first 31 unlock(&sched.deferlock) 32 }) 33 } 34 // 清空当前要释放的defer的属性 35 d.siz = 0 36 d.started = false 37 d.sp = 0 38 d.pc = 0 39 d.link = nil 40 41 pp.deferpool[sc] = append(pp.deferpool[sc], d) 42 } 复制代码 gopanic 复制代码 1 func gopanic(e interface{}) { 2 gp := getg() 3 4 var p _panic 5 p.arg = e 6 p.link = gp._panic 7 gp._panic = (*_panic)(noescape(unsafe.Pointer(&p))) 8 9 atomic.Xadd(&runningPanicDefers, 1) 10