美文网首页程序员
关于go 语言中的延迟执行函数

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

作者: golang推广大使 | 来源:发表于2019-03-12 21:49 被阅读0次

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

在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
}

相关文章

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

    许多内置的带有返回值的函数无法进行延迟调用 在go语言中,调用自定义函数的结果值可以全部不存在(丢弃)。但是,对于...

  • 2019-06-05

    Tags: 函数[多选题]Go语言中关于main函数(可执行程序的执行起点),下面说法正确的是()Amain函数不...

  • go语言defer的用法

    用法介绍: defer是Go语言中的延迟执行语句,用来添加函数结束时执行的代码,常用于释放某些已分配的资源、关闭数...

  • 深入理解 Go-Defer的机制

    defer 的作用和执行时机 go 的 defer 语句是用来延迟执行函数的,而且延迟发生在调用函数 return...

  • 【go语言学习】函数function

    函数是组织好的、可重复使用的、用于执行指定任务的代码块。Go语言中支持函数、匿名函数和闭包,并且函数在Go语言中属...

  • Go函数-延迟调用(三)

    在go语言里,defer可是实现延迟函数调用。语句defer向当前函数注册稍后执行的函数调用。这些调用被称做延迟调...

  • 第15章-defer语句

    defer 1.1 延迟是什么? 即延迟(defer)语句,延迟语句被用于执行一个函数调用,在这个函数之前,延迟语...

  • Go语言中的defer(延迟执行)

    正常的函数调用是这样的: 屏幕输出: 被延迟的函数调用时这样的: 屏幕输出: 所以defer是写在函数调用前面,使...

  • 11.***函数***

    Go语言基础之函数 Golang 函数是组织好的、可重复使用的、用于执行指定任务的代码块。本文介绍了Go语言中函数...

  • defer

    Go 语言的 defer 会在当前函数或者方法(不是main)返回之前执行函数。由于 defer 的延迟特性,de...

网友评论

    本文标题:关于go 语言中的延迟执行函数

    本文链接:https://www.haomeiwen.com/subject/dypopqtx.html