Go中的defer关键字表示了要在当前函数结束时执行的函数。常用于释放资源(例如关闭文件句柄或解锁互斥锁)
Defer语句是以关键字defer为前缀的普通函数调用,即defer someFunction()
如:
func foo() {
f, err := os.Open("myfile.txt")
if err != nil {
return
}
defer f.Close()
// 其他代码
// 其他代码
}
在上面的示例中,f.Close()
将在foo函数退出前执行。
将defer f.Close()
紧随os.Open()
之后,可以轻松确保始终调用Close.
也使用匿名函数延迟执行多条语句:
func foo() {
mutex1.Lock()
mutex2.Lock()
defer func() {
mutex2.Unlock()
mutex1.Unlock()
}()
// ... more code
}
多个defer
如果一个函数中有多个defer语句,它们将形成一个栈。 最后一个defer是在函数返回之后第一个执行的,然后依次调用先前的defer(以下示例通过触发panic而返回):
func logNum(i int) {
fmt.Printf("Num %d\n", i)
}
func main() {
defer logNum(1)
fmt.Println("First main statement")
defer logNum(2)
defer logNum(3)
panic("panic occurred")
fmt.Println("Last main statement") // not printed
// not deferred since execution flow never reaches this line
defer logNum(4)
}
First main statement
Num 3
Num 2
Num 1
panic: panic occurred
goroutine 1 [running]:
main.main()
/tmp/src963563549/main.go:17 +0x152
exit status 2
defer函数在执行时会计算其参数:
func logNum(i int) {
fmt.Printf("Num %d\n", i)
}
func main() {
i := 1
defer logNum(i) // deferred function call: logNum(1)
fmt.Println("First main statement")
i++
defer logNum(i) // deferred function call: logNum(2)
defer logNum(i * i) // deferred function call: logNum(4)
}
First main statement
Num 4
Num 2
Num 1
如果一个函数具有命名的返回值,则即使该函数返回,该函数中的defer匿名函数也可以访问和更新返回的值:
func plusOne(i int) (result int) {
// anonymous function must be called by adding ()
defer func() { result++ }()
// i is returned as result, which is updated by deferred function above
// after execution of below return
return i
}
func main() {
fmt.Println(plusOne(1)) // 2
}
2
Defer pitfalls
使用defer时,请牢记以下几点:defer函数在函数结束时调用。
defer语句是函数范围,而不是代码块范围, 换句话说:defer调用在退出函数时执行,而不是在执行使用if或for语句创建的块时执行。
func main() {
fmt.Print("Before if\n")
if true {
defer fmt.Print("inside if\n")
}
fmt.Print("Ater if\n")
}
Before if
Ater if
inside if
你可能希望该defer语句在我们退出if分支时执行,实际上是作为函数调用的最后一件事执行。
在defer函数中使用外部变量:
func main() {
for i := 0; i < 2; i++ {
defer func() {
fmt.Printf("%d\n", i)
}()
}
}
2
2
一个常见的错误是认为该代码将输出0和1。当我们看到defer时,这些就是i的值。
但是,defer创建一个仅通过引用捕获变量i的闭包。 它不捕获变量的值。
当我们看什么代码执行时,应该变得很清楚:
var i int
for i = 0; i < 2; i++ {
// create closure that references i by reference
}
fmt.Printf("%d\n", i) // from created by defer in second loop iteration (remember: reverse order)
fmt.Printf("%d\n", i) // from closure created by defer in first loop iteration
现在很明显,当我们调用deferred fmt.Printf时,我是2。
我们可以通过强制捕获变量来解决此问题:
func main() {
for i := 0; i < 2; i++ {
defer func(i2 int) {
fmt.Printf("%d\n", i2)
}(i)
}
}
1
0
闭包可能会稍微昂贵一些,因为它需要分配一个对象来收集闭包捕获的所有变量。
- 作者:luangeng
- 主页:https://wawazhua.cn
- 本文出处:https://wawazhua.cn/post/go/Defer/
- 版权声明:禁止转载-非商用-非衍生