defer会在函数返回前执行,那么我把代码,例如资源释放等,写到函数末尾不就行了,为什么使用defer?什么情况下必须使用defer,它有什么好处呢?
在使用defer的场景中,主要有以下几个情况:
1. 关闭资源:使用defer可以确保在函数执行完毕后关闭打开的文件、网络连接、数据库连接等资源。如果没有使用defer,那么可能会因为依赖外部代码调用顺序而导致资源未被正确释放,从而导致内存泄漏或其他问题。
2. 错误处理:当代码发生异常错误,需要进行一些清理工作时,使用defer可以确保这些清理工作会在函数返回前执行。这有助于在异常情况下避免资源泄露和数据一致性问题。
3. 记录日志:在函数结束时记录日志是一个常见的行为。使用defer可以让日志写入操作在函数返回前完成,确保日志记录完整。
好处:
1. 简化代码:使用defer可以将一些常规的清理任务从代码逻辑中分离出来,从而使得业务逻辑更加简洁、易读、易维护,并且能够提高代码的可重用¥。
2. 防止遗漏:使用defer能够确保即便出现异常,也会执行必要的清理操作,避免了由于遗漏清理代码而导致的资源泄露等问题。
总而言之,使用defer可以降低代码复杂度、提高可读性、避免代码逻辑错误,是一个非常实用的语言特性。
defer含义
- defer是延迟的意思,在Go里可以放在某个函数或者方法调用的前面,让该函数或方法延迟执行
- 语法:
defer function([parameter_list]) // 延迟执行函数
defer method([parameter_list]) // 延迟执行方法
- defer本身是在某个函数体内执行,比如在函数A内调用了defer func_name(),只要defer func_name()这行代码被执行到了,那func_name这个函数就会被延迟到函数A return或者panic之前执行。
注意:如果是函数是因为调用了os.Exit()
而退出,那defer
就不会被执行了。参见Go语言里被defer的函数一定会执行么?
defer func_name([parameter_list])
defer package_name.func_name([parameter_list]) // 例如defer fmt.Println("blabla")
- 如果在函数内调用了多次defer,那在函数return之前,defer的函数调用满足LIFO原则,先defer的函数后执行,后defer的函数先执行。比如在函数A内先后执行了defer f1(), defer f2(), defer f3(),那函数A return之前,会按照f3(), f2(), f1()的顺序执行,再return。
defer的用途?
Answer:defer常用于成对的操作,比如文件打开后要关闭、锁的申请和释放、sync.WaitGroup跟踪的goroutine的计数器的释放等。为了确保资源被释放,可以结合defer一起使用,避免在代码的各种条件分支里去释放资源,容易遗漏和出错。
- 示例1
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func sumN(N int) {
// 调用defer wg.Done()确保sumN执行完之后,可以对wg的计数器减1
defer wg.Done()
sum := 0
for i:=1; i<=N; i++ {
sum += i
}
fmt.Printf("sum from 1 to %d is %d\n", N, sum)
}
func main() {
// 设置wg跟踪的计数器数量为1
wg.Add(1)
// 开启sumN这个goroutine去计算1到100的和
go sumN(100)
// Wait会一直等待,直到wg的计数器为0
wg.Wait()
fmt.Println("finish")
}
- defer结合goroutine和闭包一起使用,可以让任务函数内部不用关心Go并发里的同步原语,更多内容可以参考goroutine和sync.WaitGroup
package main
import (
"fmt"
"sync"
)
func worker(id int) {
fmt.Println(id)
}
func main() {
var wg sync.WaitGroup
size := 10
wg.Add(size)
for i:=0; i<size; i++ {
i := i
/*把worker的调用和defer放在一个闭包里
这样worker函数内部就不用使用WaitGroup了
*/
go func() {
defer wg.Done()
worker(i)
}()
}
wg.Wait()
}
注意事项
- defer后面跟的必须是函数或者方法调用,defer后面的表达式不能加括号。
defer (fmt.Println(1)) // 编译报错,因为defer后面跟的表达式不能加括号
- 被defer的函数的参数在执行到defer语句的时候就被确定下来了。
func a() {
i := 0
defer fmt.Println(i) // 最终打印0
i++
return
}
上例中,被defer的函数fmt.Println的参数i在执行到defer这一行的时候,i的值是0,fmt.Println的参数就被确定下来是0了,因此最终打印的结果是0,而不是1。
- 被defer的函数执行顺序满足LIFO原则,后defer的先执行。
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}
上例中,输出的结果是3210,后defer的先执行。
- 被defer的函数可以对defer语句所在的函数的命名返回值做读取和修改操作。
// f returns 42
func f() (result int) {
defer func() {
// result is accessed after it was set to 6 by the return statement
result *= 7
}()
return 6
}
上例中,被defer的函数func对defer语句所在的函数f的命名返回值result做了修改操作。
调用函数f,返回的结果是42。
执行顺序是函数f先把要返回的值6赋值给result,然后执行被defer的函数func,result被修改为42,然后函数f返回result,也就是返回了42。