在golang中使用延迟

时间:2017-12-02 12:36:25

标签: go

golang中defer的用途是什么?语言文档说它是在周围函数返回时执行的。为什么不把代码放在给定函数的末尾?

6 个答案:

答案 0 :(得分:37)

我们通常使用defer来关闭或​​取消分配资源。

周围函数在返回之前执行所有延迟函数调用,即使它发生混乱。如果您只是在周围函数的末尾放置一个函数调用,则在发生混乱时会跳过它。

此外,延迟函数调用可以通过调用recover内置函数来处理恐慌。这不能通过函数末尾的普通函数调用来完成。

每个延迟调用都被置于堆栈中,并在周围函数结束时以相反的顺序执行。相反的顺序有助于正确释放资源。

必须达到defer语句才能调用函数。

您可以将其视为实施try-catch-finally块的另一种方式。

关闭try-finally

func main() {
    f, err := os.Create("file")
    if err != nil {
        panic("cannot create file")
    }
    defer f.Close()
    // no matter what happens here file will be closed
    // for sake of simplicity I skip checking close result
    fmt.Fprintf(f,"hello")
}

关闭和恐慌处理,如try-catch-finally

func main() {
    defer func() {
        msg := recover()
        fmt.Println(msg)
    }()
    f, err := os.Create(".") // . is a current directory
    if err != nil {
        panic("cannot create file")
    }
    defer f.Close()
    // no matter what happens here file will be closed
    // for sake of simplicity I skip checking close result
    fmt.Fprintf(f,"hello")
}

try-catch-finally的好处是没有嵌套的块和变量范围。这简化了周围功能的结构。

就像finally块一样,延迟函数调用也可以修改返回值,如果它们可以到达返回的数据。

func yes() (text string) {
    defer func() {
       text = "no"
    }()
    return "yes"
}

func main() {
    fmt.Println(yes())
}

答案 1 :(得分:4)

嗯,并不总能保证您的代码可能会到达函数的末尾(例如,错误或某些其他条件可能会迫使您在函数结束之前返回)。 defer语句确保分配给它的任何函数都会被执行,即使函数发生混乱或代码在函数结束之前返回良好。

defer语句还有助于保持代码清洁。在函数esp中有多个return语句的情况下。当一个人需要在返回之前释放资源时(例如,假设你在函数开始时有一个公开调用来访问资源 - 为了避免资源泄漏,必须在函数返回之前调用相应的close。并且说你的函数有多个return语句,可能是针对包括错误检查在内的不同条件。在这种情况下,没有延迟,通常会在每个return语句之前为该资源调用close。 defer语句确保无论函数返回的位置如何,总是调用传递给它的函数,从而使您免于进行过多的内务管理工作。

也可以在同一功能中多次调用延迟。例如:如果您通过函数分配了不同的资源,需要在返回之前最终释放,那么您可以在分配后调用每个资源的延迟,并且这些函数的执行顺序与调用它们的顺序相反。当函数退出时。

答案 2 :(得分:2)

使用defer的主要好处 - 无论函数如何返回,都会以任何方式调用它。如果发生特殊情况,将调用延迟函数。

所以它提供了很好的东西:

  1. panic之后恢复。这允许yes实现try ... catch行为。

  2. 在正常退出之前不要忘记清理(关闭文件,空闲内存等)。您可以打开一些资源,然后在退出之前必须关闭它。但是函数可以有几个退出点 - 所以你必须在每个返回点的中添加释放。这在维护方面非常繁琐。或者您只能放置一个延迟语句 - 资源将自动释放。

答案 3 :(得分:1)

  

defer语句将函数的执行推迟到   周围的函数返回。

此示例演示了defer的功能:

func elapsed(what string) func() {
    start := time.Now()
    fmt.Println("start")
    return func() {
        fmt.Printf("%s took %v\n", what, time.Since(start))
    }
}

func main() {
    defer elapsed("page")()
    time.Sleep(time.Second * 3)
}

出局:

start
page took 3s

答案 4 :(得分:1)

这里已经有很好的答案。我想再提一个用例。

func BillCustomer(c *Customer) error {
    c.mutex.Lock()
    defer c.mutex.Unlock()

    if err := c.Bill(); err != nil {
        return err
    }

    if err := c.Notify(); err != nil {
        return err
    }

    // ... do more stuff ...

    return nil
}

此示例中的defer确保无论BillCustomer如何返回,mutex都将在unlocked返回之前为BillCustomer。这非常有用,因为如果没有defer,您将不得不记住在unlock函数可能会出现的每个位置mutex。{p1

ref

答案 5 :(得分:0)

  1. 编程语言致力于提供有助于更简单,更不易出错的开发的结构。 (例如,当我们自己释放内存时,为什么Golang应该支持垃圾回收)
  2. 一个函数可以在多个点返回。用户可能会忽略在某些路径中执行某些清理操作
  3. 某些清理操作与所有返回路径都不相关
  4. 此外,最好使清理代码更接近需要清理的原始操作
  5. 当我们执行某些需要清除的操作时,我们可以“计划”在函数返回时无论发生的路径是什么,都会执行的清除操作。