go defer

本文阅读 4 分钟
首页 golang 正文

题目序号:(3241)

题目来源:腾讯

频次:1

答案1:(One)

使用defer的优势

defer一般用于资源的释放和异常的捕捉, 作为Go语言的特性之一.

defer 语句会将其后面跟随的语句进行延迟处理. 意思就是说 跟在defer后面的语言 将会在程序进行最后的return之后再执行.

在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。

1.1 资源的释放
一般我们写读取文件的代码如下

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    dst, err := os.Create(dstName)
    if err != nil {
        return 
    }
    dst.Close()
    src.Close()
    return
}

在程序最开始,os.Open及os.Create打开了两个文件资源描述符,并在最后通过file.Close方法得到释放,在正常情况下,该程序能正常运行,一旦在dstName文件创建过程中出现错误,程序就直接返回,src资源将得不到释放。因此需要在所有错误退出时释放资源,即修改为如下代码才能保证其在异常情况下的正确性。

dst, err := os.Create(dstName)
    if err != nil {
        src.Close()
    }
return

即在每个err里面如果发生了异常, 要及时关闭src的资源.
这个问题出现在加锁中也非常常见

l.lock()

// 如果下面发生了异常
// 我们需要在每个err处理块中都加入l.unlock()来解锁
// 不然资源就得不到释放, 就会产生死锁
if err != nil {
    l.unlock()
    return
}

但是这样做未免太麻烦了, defer优雅的帮我们解决了这个问题
比如我们可以这样

src, err := os.Open(srcName)
    defer src.Close()
    if err != nil {
        return
    }
    dst, err := os.Create(dstName)
    defer dst.Close()
    if err != nil {
        return 
    }
    ------------------------------------------
    l.lock()
    defer l.unlock()
    ......
    if err != nil {
        return 
    }
    ......

这样写的话, 就不需要在每个异常处理块中都加上Close() 或者 unlock()语句了

defer 常用场景

通过defer,我们可以在代码中优雅的关闭/清理代码中所使用的变量。defer作为golang清理变量的特性,有其独有且明确的行为。

defer经常和 panic 以及 recover 一起使用,判断是否有异常,进行收尾操作。

package main
 
import "fmt"
 
func main(){
    defer func(){ // 必须要先声明defer,否则不能捕获到panic异常
        fmt.Println("c")
        if err:=recover();err!=nil{
            fmt.Println(err) // 这里的err其实就是panic传入的内容,55
        }
        fmt.Println("d")
    }()
    f()
}
 
func f(){
    fmt.Println("a")
    panic(55)
    fmt.Println("b")
    fmt.Println("f")
}

输出结果:

a
c
55
d

defer 规则

  1. 当申明defer 时,参数就已经解析了
func a() {
    i := 0
    defer fmt.Println(i)   
    i++
    return
}

//输出 

上面我们说过,defer函数会在return之后被调用。那么这段函数执行完之后,是不用应该输出1呢?

读者自行编译看一下,结果输出的是0. why?

这是因为虽然我们在defer后面定义的是一个带变量的函数: fmt.Println(i). 但这个变量(i)在defer被声明的时候,就已经确定其确定的值了

  1. defer执行顺序为先进后出

当同时定义了多个defer代码块时,golang安装先定义后执行的顺序依次调用defer。

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}
// 输出
3
2
1
0
  1. defer可以读取有名返回值
func c() (i int) {
    defer func() { i++ }()
    return 1
}

我们说过defer是在return调用之后才执行的。 这里需要明确的是defer代码块的作用域仍然在函数之内,结合上面的函数也就是说,defer的作用域仍然在c函数之内。因此defer仍然可以读取c函数内的变量

本文来自投稿,不代表本站立场,如若转载,请注明出处:
syncpool的实现原理
« 上一篇 09-17
问了sync.Map(我说我对sync.Pool比较熟,就说Pool了)
下一篇 » 09-17

发表评论

发表评论