defer的底层机制
为栈操作,栈是一个先进后出的数据结构
func main() {
fmt.Println("reciprocal")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
}
运行结果
reciprocal
9
8
7
6
5
4
3
2
1
0
defer
拷贝机制
以下已经发生压栈发生值拷贝数据不再会发生变化
func test() int {
var i = 1
defer func(i int) {
fmt.Println("数据压栈已压栈数据不会在发生变化", i)
}(i)
i++
return i
}
运行结果
数据压栈已压栈数据不会在发生变化 1
函数返回值 2
defer
与return
的返回时机(执行顺序)
根据defer内的变量为匿名函数,还是命名函数。函数的返回数据会有差距
1. 计算并赋值返回值:
• 对于匿名返回值的函数,return 语句先会计算并生成返回值,并将其赋值给一个临时变量。
• 对于命名返回值的函数,return 语句会将结果赋值给命名的返回变量,这个变量在函数开始时就已经声明。
2. 执行 defer 语句:
• defer 语句中的函数在 return 语句执行后被调用,但它们的执行顺序是后进先出 (Last In First Out, LIFO) 的。
• defer 函数可以操作命名返回值,从而可能修改最终的返回值。
3. 返回结果:
• 在 defer 函数执行完后,返回最终的结果。
实践匿名函数
func main() {
fmt.Println(Anonymous())
}
func Anonymous() int {
var i int
defer func() {
i++
fmt.Println("defer2 value is ", i)
}()
defer func() {
i++
fmt.Println("defer1 in value is ", i)
}()
return i
}
结果
defer1 in value is 1
defer2 value is 2
0
执行过程:
1. 函数执行到 return i 时,i 的值是 0。
2. 执行 defer 语句,先执行 defer1,然后执行 defer2,此时 i 变为 2,但 return 的返回值已 固定为 0。
3. 最终返回值为 0。
实践命名返回值的函数
func main() {
fmt.Println(HasName())
}
func HasName() (j int) {
defer func() {
j++
fmt.Println("defer2 in value", j)
}()
defer func() {
j++
fmt.Println("defer1 in value", j)
}()
return j
}
结果
defer1 in value 1
defer2 in value 2
2
执行过程:
1. 函数执行到 return j 时,j 的初始值为 0。
2. 执行 defer 语句,先执行 defer1,j 变为 1,再执行 defer2,j 变为 2。
3. 最终返回值为 2,因为 defer 修改了命名返回值 j。
总结
• 匿名返回值函数:return 语句计算出返回值并存储在临时变量中,随后执行 defer 函数,但 defer 对返回值的修改不会影响最终返回值。
• 命名返回值函数:return 语句返回命名的返回变量,defer 函数能够修改这个变量,因此它会影响最终返回值。
defer 执行顺序的规则
1. defer 的参数在 defer 语句处计算。
2. defer 函数在函数返回时按照后进先出的顺序执行。
3. defer 函数可以读写返回函数的命名返回值。
defer 的使用场景:
1. 关闭资源
在处理文件、数据库连接、网络连接等需要关闭的资源时,defer 确保资源在使用完毕后被正确关闭,即使中间发生了错误。
2. 解锁资源
当使用 sync.Mutex 等锁机制时,defer 确保锁在临界区代码执行完毕后被解锁,从而避免死锁情况的发生。defer 释放分布式锁,防止死锁
3.记录日志或执行收尾工作
defer 可以用来记录函数的退出状态,或者在函数结束前执行一些收尾操作,例如日志记录或计时。
4. 处理错误和恢复(recover)
在可能会引发 panic 的代码中,defer 可以用来捕获和恢复错误,防止程序崩溃。
5. 清理临时状态
在函数内创建了一些临时状态或变量,需要在函数结束时清理时,defer 可以确保这些清理操作被执行。
引用
详解defer实现机制(附上三道面试题,我不信你们都能做对)