1. 延迟调用(defer)

    defer特性:

    defer用途:

    1. 2. 锁资源释放
    2. 3. 数据库连接释放

    go语言 defer

    go 语言的defer功能强大,对于资源管理非常方便,但是如果没用好,也会有陷阱。

    defer 是先进后出

    这个很自然,后面的语句会依赖前面的资源,因此如果先前面的资源先释放了,后面的语句就没法执行了。

    1. package main
    2. import "fmt"
    3. func main() {
    4. var whatever [5]struct{}
    5. for i := range whatever {
    6. defer fmt.Println(i)
    7. }
    8. }

    输出结果:

    1. 4
    2. 3
    3. 2
    4. 1
    5. 0

    defer 碰上闭包

    1. package main
    2. import "fmt"
    3. func main() {
    4. var whatever [5]struct{}
    5. for i := range whatever {
    6. defer func() { fmt.Println(i) }()
    7. }
    8. }

    输出结果:

    1. 4
    2. 4
    3. 4
    4. 4
    5. 4

    其实go说的很清楚,我们一起来看看go spec如何说的

    Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usualand saved anew but the actual function is not invoked.

    也就是说函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4,所以输出全都是4.

    defer f.Close

    这个大家用的都很频繁,但是go语言编程举了一个可能一不小心会犯错的例子.

    1. package main
    2. import "fmt"
    3. type Test struct {
    4. name string
    5. }
    6. func (t *Test) Close() {
    7. fmt.Println(t.name, " closed")
    8. }
    9. func main() {
    10. ts := []Test{{"a"}, {"b"}, {"c"}}
    11. for _, t := range ts {
    12. defer t.Close()
    13. }
    14. }

    输出结果:

    1. c closed
    2. c closed
    3. c closed

    这个输出并不会像我们预计的输出c b a,而是输出c c c

    可是按照前面的go spec中的说明,应该输出c b a才对啊.

    那我们换一种方式来调用一下.

    1. package main
    2. import "fmt"
    3. type Test struct {
    4. name string
    5. }
    6. func (t *Test) Close() {
    7. fmt.Println(t.name, " closed")
    8. }
    9. func Close(t Test) {
    10. t.Close()
    11. }
    12. func main() {
    13. ts := []Test{{"a"}, {"b"}, {"c"}}
    14. for _, t := range ts {
    15. defer Close(t)
    16. }
    17. }

    输出结果:

    1. c closed
    2. b closed
    3. a closed

    这个时候输出的就是c b a

    看似多此一举的声明

    输出结果:

    1. c closed
    2. b closed
    3. a closed

    通过以上例子,结合

    Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usualand saved anew but the actual function is not invoked.

    这句话。可以得出下面的结论:

    defer后面的语句在执行的时候,函数调用的参数会被保存起来,但是不执行。也就是复制了一份。但是并没有说struct这里的this指针如何处理,通过这个例子可以看出go语言并没有把这个明确写出来的this指针当作参数来看待。

    多个 defer 注册,按 FILO 次序执行 ( 先进后出 )。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。

    1. package main
    2. func test(x int) {
    3. defer println("a")
    4. defer println("b")
    5. defer func() {
    6. println(100 / x) // div0 异常未被捕获,逐步往外传递,最终终止进程。
    7. }()
    8. defer println("c")
    9. }
    10. func main() {
    11. test(0)
    12. }

    输出结果:

    1. c
    2. b
    3. a
    4. panic: runtime error: integer divide by zero

    *延迟调用参数在注册时求值或复制,可用指针或闭包 "延迟" 读取。

    1. package main
    2. func test() {
    3. x, y := 10, 20
    4. defer func(i int) {
    5. println("defer:", i, y) // y 闭包引用
    6. }(x) // x 被复制
    7. x += 10
    8. y += 100
    9. println("x =", x, "y =", y)
    10. }
    11. test()
    12. }

    输出结果:

    1. x = 20 y = 120
    2. defer: 10 120

    *滥用 defer 可能会导致性能问题,尤其是在一个 "大循环" 里。

    1. package main
    2. "fmt"
    3. "sync"
    4. "time"
    5. )
    6. var lock sync.Mutex
    7. func test() {
    8. lock.Lock()
    9. lock.Unlock()
    10. }
    11. func testdefer() {
    12. lock.Lock()
    13. defer lock.Unlock()
    14. }
    15. func main() {
    16. func() {
    17. t1 := time.Now()
    18. for i := 0; i < 10000; i++ {
    19. test()
    20. }
    21. elapsed := time.Since(t1)
    22. fmt.Println("test elapsed: ", elapsed)
    23. }()
    24. func() {
    25. t1 := time.Now()
    26. for i := 0; i < 10000; i++ {
    27. testdefer()
    28. }
    29. elapsed := time.Since(t1)
    30. fmt.Println("testdefer elapsed: ", elapsed)
    31. }()
    32. }

    输出结果:

    1. test elapsed: 223.162µs
    2. testdefer elapsed: 781.304µs

    1.1.2. defer陷阱

    defer 与 closure

    1. package main
    2. import (
    3. "errors"
    4. "fmt"
    5. )
    6. func foo(a, b int) (i int, err error) {
    7. defer fmt.Printf("first defer err %v\n", err)
    8. defer func(err error) { fmt.Printf("second defer err %v\n", err) }(err)
    9. defer func() { fmt.Printf("third defer err %v\n", err) }()
    10. if b == 0 {
    11. err = errors.New("divided by zero!")
    12. return
    13. }
    14. i = a / b
    15. return
    16. }
    17. func main() {
    18. foo(2, 0)
    19. }

    输出结果:

    1. third defer err divided by zero!
    2. second defer err <nil>
    3. first defer err <nil>

    解释:如果 defer 后面跟的不是一个 closure 最后执行的时候我们得到的并不是最新的值。

    defer 与 return

    输出结果:

    1. 2

    解释:在有具名返回值的函数中(这里具名返回值为 i),执行 return 2 的时候实际上已经将 i 的值重新赋值为 2。所以defer closure 输出结果为 2 而不是 1。

    defer nil 函数

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. func test() {
    6. var run func() = nil
    7. defer run()
    8. fmt.Println("runs")
    9. }
    10. func main() {
    11. defer func() {
    12. if err := recover(); err != nil {
    13. fmt.Println(err)
    14. }
    15. }()
    16. test()
    17. }
    1. runs
    2. runtime error: invalid memory address or nil pointer dereference

    解释:名为 test 的函数一直运行至结束,然后 defer 函数会被执行且会因为值为 nil 而产生 panic 异常。然而值得注意的是,run() 的声明是没有问题,因为在test函数运行完成后它才会被调用。

    在错误的位置使用 defer

    当 http.Get 失败时会抛出异常。

    1. package main
    2. import "net/http"
    3. func do() error {
    4. res, err := http.Get("http://www.google.com")
    5. defer res.Body.Close()
    6. if err != nil {
    7. return err
    8. }
    9. // ..code...
    10. return nil
    11. }
    12. func main() {
    13. do()
    14. }

    输出结果:

    1. panic: runtime error: invalid memory address or nil pointer dereference

    因为在这里我们并没有检查我们的请求是否成功执行,当它失败的时候,我们访问了 Body 中的空变量 res ,因此会抛出异常

    解决方案

    总是在一次成功的资源分配下面使用 defer ,对于这种情况来说意味着:当且仅当 http.Get 成功执行时才使用 defer

    1. package main
    2. import "net/http"
    3. func do() error {
    4. res, err := http.Get("http://xxxxxxxxxx")
    5. if res != nil {
    6. defer res.Body.Close()
    7. }
    8. return err
    9. }
    10. // ..code...
    11. return nil
    12. }
    13. func main() {
    14. do()
    15. }

    在上述的代码中,当有错误的时候,err 会被返回,否则当整个函数返回的时候,会关闭 res.Body 。

    解释:在这里,你同样需要检查 res 的值是否为 nil ,这是 http.Get 中的一个警告。通常情况下,出错的时候,返回的内容应为空并且错误会被返回,可当你获得的是一个重定向 error 时, res 的值并不会为 nil ,但其又会将错误返回。上面的代码保证了无论如何 Body 都会被关闭,如果你没有打算使用其中的数据,那么你还需要丢弃已经接收的数据。

    不检查错误

    在这里,f.Close() 可能会返回一个错误,可这个错误会被我们忽略掉

    1. package main
    2. import "os"
    3. func do() error {
    4. f, err := os.Open("book.txt")
    5. if err != nil {
    6. return err
    7. }
    8. if f != nil {
    9. defer f.Close()
    10. }
    11. // ..code...
    12. return nil
    13. }
    14. func main() {
    15. do()
    16. }

    改进一下

    1. package main
    2. import "os"
    3. func do() error {
    4. f, err := os.Open("book.txt")
    5. if err != nil {
    6. return err
    7. }
    8. if f != nil {
    9. defer func() {
    10. if err := f.Close(); err != nil {
    11. // log etc
    12. }
    13. }()
    14. }
    15. // ..code...
    16. return nil
    17. }
    18. func main() {
    19. do()
    20. }

    再改进一下

    通过命名的返回变量来返回 defer 内的错误。

    1. package main
    2. import "os"
    3. func do() (err error) {
    4. f, err := os.Open("book.txt")
    5. if err != nil {
    6. return err
    7. }
    8. if f != nil {
    9. defer func() {
    10. if ferr := f.Close(); ferr != nil {
    11. err = ferr
    12. }
    13. }()
    14. }
    15. // ..code...
    16. return nil
    17. }
    18. func main() {
    19. do()
    20. }

    释放相同的资源

    如果你尝试使用相同的变量释放不同的资源,那么这个操作可能无法正常执行。

    输出结果: defer close book.txt err close ./another-book.txt: file already closed

    当延迟函数执行时,只有最后一个变量会被用到,因此,f 变量 会成为最后那个资源 (another-book.txt)。而且两个 defer 都会将这个资源作为最后的资源来关闭

    解决方案:

    1. package main
    2. import (
    3. "fmt"
    4. "io"
    5. "os"
    6. )
    7. func do() error {
    8. f, err := os.Open("book.txt")
    9. if err != nil {
    10. return err
    11. }
    12. if f != nil {
    13. defer func(f io.Closer) {
    14. if err := f.Close(); err != nil {
    15. fmt.Printf("defer close book.txt err %v\n", err)
    16. }
    17. }(f)
    18. }
    19. // ..code...
    20. f, err = os.Open("another-book.txt")
    21. if err != nil {
    22. return err
    23. }
    24. if f != nil {
    25. defer func(f io.Closer) {
    26. if err := f.Close(); err != nil {
    27. fmt.Printf("defer close another-book.txt err %v\n", err)
    28. }
    29. }(f)
    30. }
    31. return nil
    32. }
    33. func main() {
    34. do()