Loading

Go语言精进之路读书笔记第22条——使用defer让函数更简介、更健壮

22.1 defer的运行机制

  • 在Go中,只有在函数和方法内部才能使用defer。
  • defer关键字后面只能接函数或方法,这些函数被成为deferred函数。defer将它们注册到其所在goroutine用于存放deferred函数的栈数据结构中。
  • 在执行defer的函数退出前,按后进先出(LIFO)的顺序调度执行。

22.2 defer的常见用法

最基本、最常见:用于释放资源

1.拦截panic

按需要对panic进行处理,可以尝试从panic中回复(也是Go语言中唯一的从panic中恢复的手段),也可以触发一个新的panic,但为panic传一个新的error值

// $GOROOT/src/bytes/buffer.go
func makeSlice(n int) []byte {
    // If the make fails, give a known error.
    defer func() {
        if recover() != nil {
            panic(ErrTooLarge)
        }
    }()
    return make([]byte, n)
}

deferred函数虽然可以拦截绝大部分的panic,但无法拦截并恢复一些运行时之外的致命问题,比如通过C代码“制造”的崩溃。

2.修改函数的具名返回值

// $GOROOT/src/fmt/scan.go
func (s *ss) Token(skipSpace bool, f func(rune) bool) (tok []byte, err error) {
    defer func() {
        if e := recover(); e != nil {
            if se, ok := e.(scanError); ok {
                err = se.err
            } else {
                panic(e)
            }
        }
    }()
    ...
}
// $GOROOT/src/net/ipsock_plan9.go
func dialPlan9(ctx context.Context, net string, laddr, raddr Addr) (fd *netFD, err error) {
    defer func() { fixErr(err) }()
    ...
}

// 再来一个更直观的例子
func foo(a, b int) (x, y int) {
    defer func() {
        x = x * 5
        y = y * 10
    }()

    x = a + 5
    y = b + 6
    return
}

3.输出调试信息

// $GOROOT/src/net/conf.go
func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrder) {
    if c.dnsDebugLevel > 1 {
        defer func() {
            print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n")
        }()
    }
    ...
}

在出入函数时打印留痕日志(一般在debug日志级别下)

func trace(s string) string {
    fmt.Println("entering:", s)
    return s
}
func un(s string) {
    fmt.Println("leaving:", s)
}
func a() {
    defer un(trace("a"))
    fmt.Println("in a")
}
func b() {
    defer un(trace("b"))
    fmt.Println("in b")
    a()
}
func main() {
    b()
}

4.还原变量旧值

// $GOROOT/src/syscall/fs_nacl.go
func init() {
    oldFsinit := fsinit
    defer func() { fsinit = oldFsinit }()
    fsinit = func() {}
    ...
}

先将fsinit存储在局部变量oldFsinit中,然后在deferred函数将fsinit的值重新置为存储在oldFsinit中的旧值。

22.3 关于defer的几个关键问题

1.明确哪些函数可以作为deferred函数

内置函数append/cap/complex/imag/len/make/new/real不可以直接作为deferred函数

func bar() (int, int) {
    return 1, 2
}

func fooD() {
    // builtin functions:
    //    append cap close complex copy delete imag len
    //     make new panic print println real recover

    var c chan int
    var sl []int
    var m = make(map[string]int, 10)
    m["item1"] = 1
    m["item2"] = 2
    var a = complex(1.0, -1.4)

    var sl1 []int

    defer bar()
    //defer append(sl, 11)
    //defer cap(sl)
    defer close(c)
    //defer complex(2, -2)
    defer copy(sl1, sl)
    defer delete(m, "item2")
    //defer imag(a)
    //defer len(sl)
    //defer make([]int, 10)
    //defer new(*int)
    defer panic(1)
    defer print("hello, defer\n")
    defer println("hello, defer")
    //defer real(a)
    defer recover()
}
  • 对于有返回值的自定义函数或方法,返回值会在deferred函数被调用执行的时候被自动丢弃
  • 对于哪些不能直接作为deferred函数的内置函数,可以使用一个包裹不能直接作为deferred函数的匿名函数来间接满足要求(意义不大)
defer func() {
    _ = append(s1, 11)
}

2.把握好defer关键字后表达式的求值时机

defer关键字后面的表达式是在将deferred函数注册到deferred函数栈的时候进行求值的。

例子1

  • 函数&带参数的匿名函数,传入for循环每次i的值
  • 不带参数的匿名函数,传入for循环结束后i=4
func foo1() {
    for i := 0; i <= 3; i++ {
        defer fmt.Println(i)
    }
}

func foo2() {
    for i := 0; i <= 3; i++ {
        defer func(n int) {
            fmt.Println(n)
        }(i)
    }
}

func foo3() {
    for i := 0; i <= 3; i++ {
        defer func() {
            fmt.Println(i)
        }()
    }
}

func main() {
    fmt.Println("foo1 result:")
    foo1() //3 2 1 0
    fmt.Println("\nfoo2 result:")
    foo2() //3 2 1 0
    fmt.Println("\nfoo3 result:")
    foo3() //4 4 4 4
}

例子2

  • foo11:传入的变量s1的值为[]int{1,2,3},因此压入deferred函数栈的函数是func([]int{1,2,3})
  • foo22:传入的变量为s1的地址,因此压入deferred函数栈的函数是func(&s1)
func foo11() {
    sl := []int{1, 2, 3}
    defer func(a []int) {
        fmt.Println(a)
    }(sl)

    sl = []int{3, 2, 1}
    _ = sl
}
func foo22() {
    sl := []int{1, 2, 3}
    defer func(p *[]int) {
        fmt.Println(*p)
    }(&sl)

    sl = []int{3, 2, 1}
    _ = sl
}

func main() {
    foo11() // [1 2 3]
    foo22() // [3 2 1]
}

3.知晓defer带来的性能损耗

Go1.14版本中,defer性能提升巨大,已经和不使用defer的性能相差很小了

posted @ 2024-02-13 11:18  brynchen  阅读(12)  评论(0编辑  收藏  举报