58

Go语言中defer的一些坑

 6 years ago
source link: https://studygolang.com/articles/14831?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

defer语句是Go中一个非常有用的特性,可以将一个方法延迟到包裹该方法的方法返回时执行,在实际应用中,defer语句可以充当其他语言中try…catch…的角色,也可以用来处理关闭文件句柄等收尾操作。

defer触发时机

A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.

Go官方文档中对defer的执行时机做了阐述,分别是。

  • 包裹defer的函数返回时
  • 包裹defer的函数执行到末尾时
  • 所在的goroutine发生panic时

defer执行顺序

当一个方法中有多个defer时, defer会将要延迟执行的方法“压栈”,当defer被触发时,将所有“压栈”的方法“出栈”并执行。所以defer的执行顺序是LIFO的。

所以下面这段代码的输出不是1 2 3,而是3 2 1。

func stackingDefers() {
    defer func() {
        fmt.Println("1")
    }()
    defer func() {
        fmt.Println("2")
    }()
    defer func() {
        fmt.Println("3")
    }()
}

坑1:defer在匿名返回值和命名返回值函数中的不同表现

先看下面两个方法执行的结果。

func returnValues() int {
    var result int
    defer func() {
        result++
        fmt.Println("defer")
    }()
    return result
}

func namedReturnValues() (result int) {
    defer func() {
        result++
        fmt.Println("defer")
    }()
    return result
}

上面的方法会输出0,下面的方法输出1。上面的方法使用了匿名返回值,下面的使用了命名返回值,除此之外其他的逻辑均相同,为什么输出的结果会有区别呢?

要搞清这个问题首先需要了解defer的执行逻辑,文档中说defer语句在方法返回“时”触发,也就是说return和defer是“同时”执行的。以匿名返回值方法举例,过程如下。

  • 将result赋值给返回值(可以理解成Go自动创建了一个返回值retValue,相当于执行retValue = result)
  • 然后检查是否有defer,如果有则执行
  • 返回刚才创建的返回值(retValue)

在这种情况下,defer中的修改是对result执行的,而不是retValue,所以defer返回的依然是retValue。在命名返回值方法中,由于返回值在方法定义时已经被定义,所以没有创建retValue的过程,result就是retValue,defer对于result的修改也会被直接返回。

坑2:在for循环中使用defer可能导致的性能问题

看下面的代码

func deferInLoops() {
    for i := 0; i < 100; i++ {
        f, _ := os.Open("/etc/hosts")
        defer f.Close()
    }
}

defer在紧邻创建资源的语句后生命力,看上去逻辑没有什么问题。但是和直接调用相比,defer的执行存在着额外的开销,例如defer会对其后需要的参数进行内存拷贝,还需要对defer结构进行压栈出栈操作。所以在循环中定义defer可能导致大量的资源开销,在本例中,可以将f.Close()语句前的defer去掉,来减少大量defer导致的额外资源消耗。

坑3:判断执行没有err之后,再defer释放资源

一些获取资源的操作可能会返回err参数,我们可以选择忽略返回的err参数,但是如果要使用defer进行延迟释放的的话,需要在使用defer之前先判断是否存在err,如果资源没有获取成功,即没有必要也不应该再对资源执行释放操作。如果不判断获取资源是否成功就执行释放操作的话,还有可能导致释放方法执行错误。

正确写法如下。

resp, err := http.Get(url)
// 先判断操作是否成功
if err != nil {
    return err
}
// 如果操作成功,再进行Close操作
defer resp.Body.Close()

坑4:调用os.Exit时defer不会被执行

当发生panic时,所在goroutine的所有defer会被执行,但是当调用os.Exit()方法退出程序时,defer并不会被执行。

func deferExit() {
    defer func() {
        fmt.Println("defer")
    }()
    os.Exit(0)
}

上面的defer并不会输出。

点击关注知乎专栏 Golang私房菜


Recommend

  • 148
    • 微信 mp.weixin.qq.com 7 years ago
    • Cache

    Go 延迟函数 defer 详解

    Go 语言中延迟函数 defer 充当着 try...catch 的重任,使用起来也非常简便,然而在实际应用中,很多 gopher 并没有真正搞明白 defer、return、返回值、panic 之间的执行顺序,从而掉进坑中,今天我们就来揭开它的神秘面纱!先来运行下面两段代码:A....

  • 77

    #1 — Deferred nil funcIf a deferred func evaluates to nil, execution panics when the surrounding func ends not when defer...

  • 62
    • studygolang.com 6 years ago
    • Cache

    golang 中的闭包和defer

    golang中的defer和闭包对很多初学者来说,有时候有很多坑,但是很多介绍的文章有写的乱七八糟.放假了没事可干,就稍微总结一下. 闭包 闭包有叫匿名函数,使用闭包可以使我们的代码更加优雅简洁,顾名思义匿名函数就是没有...

  • 59
    • jiajunhuang.com 6 years ago
    • Cache

    Go语言的defer, panic和recover

    本文源自 https://blog.golang.org/defer-panic-and-recover defer defer语句在Go的函数中属于一个栈的形式,即在函数运行完成时,根据该语句...

  • 52
    • studygolang.com 6 years ago
    • Cache

    Defer, Panic, Recover

    1、简介 Go具有控制流程的常用机制:if,for,switch,goto。 它还有go语句在单独的goroutine中运行代码。 在这里,我想讨论一些不太常见的问题:Defer,Panic和Recover。 2、Defer Defer语句将函数调...

  • 64
    • www.tuicool.com 5 years ago
    • Cache

    defer 的一些用法和猜测

    以前的代码中,基本上只使用了 defer 作为防御程序 panic 退出的手段,没有仔细考虑过对返回值的影响。今天有同事提到: 如果执行过程中发生 panic,defer函数 recover() != nil 的情况下,未命名的返回值的函数会...

  • 30

    golang中的slice很灵活,功能也很强悍,不过对于初学者来说会容易被它坑到,此篇文章就尽量提及到使用slice的一些容易容易出错的地方,以下示例使用的golang版本为1.14.2。 作为参数传递 在go语言中的方法的参数都为...

  • 3
    • studygolang.com 3 years ago
    • Cache

    Go 语言中的一些非常规优化

    Go 语言中的一些非常规优化 Xargin · 大约12小时之前 · 127 次点击 · 预计阅读时间 5 分钟 · 大约8小时之前 开始浏览    

  • 7

    Go语言中 defer 使用场景及注意事项,你是要注意的! goCenter · 11天之前 · 156 次点击 · 预计阅...

  • 6
    • discretetom.github.io 1 year ago
    • Cache

    Zig语言如何解决C语言中的一些问题

    原文:Problems of C, and how Zig addresses them。本文结合自己的理解,做了很多魔改 使用 comp...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK