关于go 语言中的延迟执行函数 - Go语言中文社区

关于go 语言中的延迟执行函数


许多内置的带有返回值的函数无法进行延迟调用

在go语言中,调用自定义函数的结果值可以全部不存在(丢弃)。但是,对于具有非空白返回结果列表的内置函数,他们的调用的结果不可以抛弃,copy和recover例外。 换句话说,延迟执行函数的结果必须被抛弃,所以许多内置函数无法被延迟。
幸运的是,在实践中,很有偶需要延迟执行内置函数的地方。据我所知,只有append函数可能需要被延迟执行。这种情况下,我们可以把append包装到一个延迟执行函数里。

package main

import "fmt"

func main() {
    s := []string{"a", "b", "c", "d"}
    defer fmt.Println(s) // [a x y d]
    // defer append(s[:1], "x", "y") // error
    defer func() {
        _ = append(s[:1], "x", "y")
    }()
}

延迟函数值的时刻

延迟函数调用中的被调用函数可以是零函数值。对于这种情况,恐慌将在准备延迟调用被推入当前goroutine的延迟调用栈之前发生。

package main

import "fmt"

func main() {
    defer fmt.Println("reachable")
    var f func() // f is nil by default
    defer f()    // panic here
    // The following lines are dead code.
    fmt.Println("not reachable")
    f = func() {}
}

在将延迟调用推入当前goroutine的延迟调用堆栈之前,还会计算延迟函数调用的参数

延迟调用使得代码清晰,并且bug少

import "os"

func withoutDefers(filepath string, head, body []byte) error {
    f, err := os.Open(filepath)
    if err != nil {
        return err
    }

    _, err = f.Seek(16, 0)
    if err != nil {
        f.Close()
        return err
    }

    _, err = f.Write(head)
    if err != nil {
        f.Close()
        return err
    }

    _, err = f.Write(body)
    if err != nil {
        f.Close()
        return err
    }

    err = f.Sync()
    f.Close()
    return err
}

func withDefers(filepath string, head, body []byte) error {
    f, err := os.Open(filepath)
    if err != nil {
        return err
    }
    defer f.Close()

    _, err = f.Seek(16, 0)
    if err != nil {
        return err
    }

    _, err = f.Write(head)
    if err != nil {
        return err
    }

    _, err = f.Write(body)
    if err != nil {
        return err
    }

    return f.Sync()
}

延迟函数调用导致的性能损失

使用延迟函数调用并不总是好的。到目前为止(Go 1.12),对于官方Go编译器,延迟函数调用将在运行时导致一些性能损失.
例如,在以下示例中,CounterB和IncreaseB方法比CounterA和IncreaseA方法更有效。

import "sync"

type T struct {
    mu sync.Mutex
    n  int64
}

func (t *T) CounterA() int64 {
    t.mu.Lock()
    defer t.mu.Unlock()
    return t.n
}

func (t *T) CounterB() (count int64) {
    t.mu.Lock()
    count = t.n
    t.mu.Unlock()
    return
}

func (t *T) IncreaseA() {
    t.mu.Lock()
    defer t.mu.Unlock()
    t.n++
}

func (t *T) IncreaseB() {
    t.mu.Lock()
    t.n++ // this line will not panic for sure
    t.mu.Unlock()
}

在B版本的函数中,我们应该保证Lock和Unlock调用之间的代码永远不会出现恐慌。通常,建议在实践中使用A版本功能。当我们真正关心所涉及功能的性能时,我们应该只采用B版本。

延迟函数调用导致的资源泄漏

非常大的延迟调用堆栈也可能消耗大量内存,并且未执行的延迟调用可能会阻止某些资源及时释放。例如,如果在调用以下函数时需要处理许多文件,则在函数退出之前将不会释放大量文件处理程序。

func writeManyFiles(files []File) error {
    for _, file := range files {
        f, err := os.Open(file.path)
        if err != nil {
            return err
        }
        defer f.Close()

        _, err = f.WriteString(file.content)
        if err != nil {
            return err
        }

        err = f.Sync()
        if err != nil {
            return err
        }
    }

    return nil
}

对于这种情况,我们可以使用匿名函数来包含延迟调用,以便延迟函数调用将更早执行。例如,上述功能可以重写和改进

func writeManyFiles(files []File) error {
    for _, file := range files {
        if err := func() error {
            f, err := os.Open(file.path)
            if err != nil {
                return err
            }
            defer f.Close()

            _, err = f.WriteString(file.content)
            if err != nil {
                return err
            }

            return f.Sync()
        }(); err != nil {
            return err
        }
    }

    return nil
}
版权声明:本文来源简书,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://www.jianshu.com/p/441c016f527e
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-01-12 12:58:23
  • 阅读 ( 830 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢