defer是什么?
在Go语言中,可以使用关键字defer向函数注册退出调用,即主函数退出时,defer后的函数才被调用。
defer语句的作用是不管程序是否出现异常,均在函数退出时自动执行相关代码。
package main
import "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 1111
test 2222
test defer 2222
test defer 1111
*/
}
defer定义顺序与实际执行顺序相反
package main
import "fmt"
/**
栈 先进后出
cccccccccccccc
bbbbbbbbbbbbbb
aaaaaaaaaaaaaa
*/
func main() {
defer fmt.Println("aaaaaaaaaaaaaa")
defer fmt.Println("bbbbbbbbbbbbbb")
defer fmt.Println("cccccccccccccc")
/**
cccccccccccccc
bbbbbbbbbbbbbb
aaaaaaaaaaaaaa
*/
}
defer执行时的拷贝机制
package main
import "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 := 1
defer func() {
result++
}()
return i
/**
延迟函数的执行正是在return之前,即加入defer后的执行过程如下:
result = i
result++
return
所以上面函数实际返回i++值 为2
*/
}
// 主函数拥有匿名返回值,返回字面量
func foo() int {
var i int
defer func() {
i++
}()
return 1
/**
返回一个局部变量,同时defer函数也会操作这个局部变量。
对于匿名返回值来说,可以假定仍然有一个变量存储返回值,假定返回值变量为“anony”,上面的返回语句可以拆分成一下过程:
anony=i
i++
return
由于i是整形,会将值拷贝给anony,所以defer语句中修改i值,对函数返回值不造成影响,最终返回1
*/
}
// 主函数拥有具名返回值
/**
主函数声明语句中带名字的返回值,会被初始化成一个局部变量,函数内部可以像使用局部变量一样使用该返回值。如果defer语句操作该返回值,可能会改变返回结果。
*/
func foo2() (ret int) {
defer func() {
ret++
}()
return 0
/**
上面的函数拆解出来,如下所示:
ret = 0
ret++
return
函数真正返回前,在defer中对返回值做了+1操作,所以函数最终返回1
*/
}
func main() {
fmt.Println(deferFuncReturn()) // 2
fmt.Println(foo()) // 1
fmt.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 1111
test defer 2222
test defer 1111
panic:
goroutine 1 [running]:
main.main()
go-test/defer.go:7 +0xf8
Process 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.Mutex
var count = 0
func 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