defer是什么?
在Go语言中,可以使用关键字defer向函数注册退出调用,即主函数退出时,defer后的函数才被调用。
defer语句的作用是不管程序是否出现异常,均在函数退出时自动执行相关代码。
package mainimport "fmt"func main() {fmt.Println("test 1111")defer fmt.Println("test defer 1111")defer fmt.Println("test defer 2222")a := 10// defer a++ 编译报错 defer 后面只能跟函数调用fmt.Println("test 2222")/**test 1111test 2222test defer 2222test defer 1111*/}
defer定义顺序与实际执行顺序相反
package mainimport "fmt"/**栈 先进后出ccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa*/func main() {defer fmt.Println("aaaaaaaaaaaaaa")defer fmt.Println("bbbbbbbbbbbbbb")defer fmt.Println("cccccccccccccc")/**ccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa*/}
defer执行时的拷贝机制
package mainimport "fmt"func defer2() {x := 10// 压栈 压栈时参数进行了值拷贝,不受x++影响defer func(a int) {fmt.Println(a) // 10}(x)x++ // x 压栈}func defer3() {x := 10// 进行了压栈,但并不是传参defer func() {// 闭包的概念fmt.Println(x) // 11}()x++}func defer4() {x := 10// 进行了压栈,引用传递 指针地址defer func(x *int) {// 闭包的概念fmt.Println(*x) // 11}(&x)x++}func main() {defer2()defer3();defer4();}
defer 注意要点
// 函数返回过程func deferFuncReturn() (result int) {i := 1defer func() {result++}()return i/**延迟函数的执行正是在return之前,即加入defer后的执行过程如下:result = iresult++return所以上面函数实际返回i++值 为2*/}// 主函数拥有匿名返回值,返回字面量func foo() int {var i intdefer func() {i++}()return 1/**返回一个局部变量,同时defer函数也会操作这个局部变量。对于匿名返回值来说,可以假定仍然有一个变量存储返回值,假定返回值变量为“anony”,上面的返回语句可以拆分成一下过程:anony=ii++return由于i是整形,会将值拷贝给anony,所以defer语句中修改i值,对函数返回值不造成影响,最终返回1*/}// 主函数拥有具名返回值/**主函数声明语句中带名字的返回值,会被初始化成一个局部变量,函数内部可以像使用局部变量一样使用该返回值。如果defer语句操作该返回值,可能会改变返回结果。*/func foo2() (ret int) {defer func() {ret++}()return 0/**上面的函数拆解出来,如下所示:ret = 0ret++return函数真正返回前,在defer中对返回值做了+1操作,所以函数最终返回1*/}func main() {fmt.Println(deferFuncReturn()) // 2fmt.Println(foo()) // 1fmt.Println(foo2()) // 1}
异常情况 (finally)
defer语句的作用是不管程序是否出现异常,均在函数退出时自动执行相关代码
func main() {fmt.Println("test 1111")defer fmt.Println("test defer 1111")defer fmt.Println("test defer 2222")panic("")fmt.Println("test 2222")/**test 1111test defer 2222test defer 1111panic:goroutine 1 [running]:main.main()go-test/defer.go:7 +0xf8Process finished with the exit code 2*/}
defer的实现原理
1.defer数据结构
type _defer struct {sp uintptr //函数栈指针pc uintptr //程序计数器fn *funcval //函数地址link *_defer //指向自身结构的指针,用于链接多个defer}
我们知道defer后面一定要接一个函数的,所以defer的数据结构根一般函数类似,也有栈指针、程序计数器、函数地址等等。
与函数不同的一点是它含有一个指针,可用于指向另一个defer,每个goroutine数据结构中实际上也有一个defer指针,该指针指向一个defer的链表,每次声明一个defer时就将defer插入到单链表表头,每次执行defer就从单链表表头取出一个defer执行。

从上图可以看到,新声明的defer总是添加到链表头部。
函数返回前执行defer则是从链表首部依次取出执行,不再赘述。
一个goroutine可能连续调用多个函数,defer添加过程跟上述流程一致,进入函数时添加defer,离开函数时取出defer,所以即便调用多个函数,也总是能保证defer是按FIFO方式执行的。
2.defer的创建和执行
源码包src/runtime/panic.go定义了两个方法分别用于创建defer和执行defer。
- deferproc(): 在声明defer处调用,将其defer函数存入goroutine的链表中;
- deferreturn(): 在return指令,准确的讲是在ret指令前调用,将其defer从goroutine链表中取出并执行
可以这么理解,在编译阶段,声明defer处插入了函数deferproc(),在函数return前插入了函数deferreturn()
[
](https://blog.csdn.net/zhongcanw/article/details/89918343)
defer 表达式的使用场景
defer 通常用于 open/close, connect/disconnect, lock/unlock 等这些成对的操作, 来保证在任何情况下资源都被正确释放.
例如:
var mutex sync.Mutexvar count = 0func increment() {mutex.Lock()defer mutex.Unlock()count++}
在increment 函数中, 我们为了避免竞态条件的出现, 而使用了 Mutex 进行加锁. 而在进行并发编程时, 加锁了却忘记(或某种情况下 unlock 没有被执行), 往往会造成灾难性的后果. 为了在任意情况下, 都要保证在加锁操作后, 都进行对应的解锁操作, 我们可以使用 defer 调用解锁操作.
参考
https://blog.csdn.net/zhongcanw/article/details/89918343
https://blog.csdn.net/huang_yong_peng/article/details/82950743
https://segmentfault.com/a/1190000006823652
https://zhuanlan.zhihu.com/p/63354092
