Go语言defer关键字
- defer关键字用于延缓函数的执行
- 只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。当defer语句被执行时,跟在defer后面的函数就会被延迟执行。直到包含该defer语句的函数执行完毕时,defer后面的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。
- defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。
- 对文件的操作的例子
package ioutil
func ReadFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
return ReadAll(f)
}
- 处理互斥锁的例子
var mu sync.Mutex
var m = make(map[string]int)
func lookup(key string) int {
mu.Lock()
defer mu.Ulock()
return m[key]
}
- 调试复杂程序时,defer机制也常被用于记录何时进入和退出函数。
- 下例中的bigSlowOperation函数,直接调用trace记录函数的被调情况。bigSlowOperation函数被调时,trace会返回一个函数值,该函数值会在bigSlowOperation退出时被调用。通过这种方式,我们可以只通过一条语句控制函数的入口和所有的出口,甚至可以记录函数的运行时间,如例子中的start。
func bigSlowOperation() {
defer trace("bigSlowOPeration")()
extra parentheses
//lot of works
time.Sleep(10 * time.Second) //simulate slow
operation by sleeping
}
func trace(msg string) func() {
start := time.Now()
log.Printf("enter %s", msg)
return func() {
log.Printf("exit %s (%s)", msg, tie.Since(start))
}
}
- defer语句中的函数会在return语句更新返回值变量后再执行,又因为在函数定义的匿名函数可以访问该函数包括返回值变量内的所有变量,所以,对匿名函数采用defer机制,可以使其观察函数的返回值
func double(x int) (result int) {
defer func() {fmt.Printf("double(%d) = %d\n", x, result)}()
return x + x
}
_ = double(4) //"8"
- 被延迟执行的匿名函数甚至可以修改函数返回给调用者的返回值
func triple(x int) (result int) {
defer func() {return += x}()
return double(x)
}
fmt.Println(triple(4))
在循环体中的defer语句特别需要注意。因为只有在函数执行完毕后,这些被延迟的函数才会执行。
下面的代码会导致系统的文件描述符耗尽,因为所有文件都被处理之前,没有文件会被关闭
for _, filename := range filenames {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
descriptors
//...
}
- 一种方法是将循环体中的defer语句移至另外一个函数。在每次循环时,调用这个函数
for _, filename := range filenames {
if err := doFile(filename); err != nil {
return err
}
}
func doFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
...
}