nav_path: best_practice
Go语言中的defer特性详解
在Go语言中,defer
关键字用于预定函数或方法的执行,这常用于处理成对的操作如打开关闭文件、加解锁、记录时间等。defer
的独特之处在于,无论包含它的函数通过何种路径返回,它都确保调用被defer
的函数。
示例分析
示例1:defer在函数正常返回前执行
// defer1.go
package main
import (
"fmt"
)
func test1() {
fmt.Println("test")
}
func main() {
fmt.Println("main start")
defer test1()
fmt.Println("main end")
}
执行结果:
main start
main end
test
这里defer test1()
确保了test1()
在main
函数的最末尾执行。
示例2:defer在函数因panic结束前执行
// defer2.go
package main
import (
"fmt"
)
func test1() {
fmt.Println("test")
}
func test2() {
panic(1)
}
func main() {
fmt.Println("main start")
defer test1()
test2()
fmt.Println("main end")
}
执行结果:
main start
test
panic: 1
...
尽管test2()
触发了panic
,defer test1()
依然得到了执行。
探究:defer是否总会执行?
现在考虑以下代码:
// defer3.go
package main
import (
"fmt"
"os"
)
func test1() {
fmt.Println("test")
}
func main() {
fmt.Println("main start")
defer test1()
fmt.Println("main end")
os.Exit(0)
}
执行结果是:
main start
main end
defer的test1()
并未执行。为何会这样?
这是因为os.Exit
直接退出当前程序,不会执行任何已经defer
的函数。
defer的四个基本原则回顾
defer
后必须是函数或方法调用,不能添加括号。defer
的函数参数在执行defer
表达式时固定。defer
的执行顺序是后进先出(LIFO)。defer
可以修改所属函数的命名返回值。
Go语言的defer实现原理
type _defer struct {
siz int32 // 参数和返回值的内存大小
started bool
heap bool // 是否分配在堆上
openDefer bool // 是否进行了优化
sp uintptr // 栈指针
pc uintptr // 程序计数器
fn *funcval // 被defer的函数
_panic *_panic // defer的panic信息
link *_defer // defer链表
}
Go语言内部通过一个链表维护defer调用,这也解释了为何它们会按LIFO顺序执行。
总结
在Go中使用defer
需要记住:并非所有情况下defer
都会执行。例如,os.Exit
会导致程序立即退出,此时defer
不会被调用。了解defer
的原理和限制能帮助我们更合理地编写Go代码。