In-depth understanding of functions in Go language [delay call defer] 12


delay calling defer

defer features:

    1. 关键字 defer 用于注册延迟调用。
    2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。
    3. 多个defer语句,按先进后出的方式执行。
    4. defer语句中的变量,在defer声明时就决定了。

Use of defer:

    1. 关闭文件句柄
    2. 锁资源释放
    3. 数据库连接释放
  • defer is used to register a deferred call (called before the function returns).
  • The typical application scenario of defer is to release resources, such as closing file handles and releasing database connections.
  • If there are multiple defers in the same function, the one registered later will be executed first.
  • Defer can be followed by a func. If a panic occurs inside the func, the panic will be put on hold temporarily, and this will be executed after the other defers are executed.
  • After defer is not followed by func, but directly followed by an execution statement, the relevant variables will be copied or calculated when registering defer.
func basic() {
    
    
    fmt.Println("A")
    defer fmt.Println(1)
    //如果同一个函数里有多个defer,则后注册的先执行
    defer fmt.Println(2)
    fmt.Println("C")
}
// A C 2 1
func defer_exe_time() (i int) {
    
    
	i = 9
	defer func() {
    
     //defer后可以跟一个func
		fmt.Printf("first i=%d\n", i) //打印5,而非9。充分理解“defer在函数返回前执行”的含义,不是在“return语句前执行defer”
	}()
	defer func(i int) {
    
    
		fmt.Printf("second i=%d\n", i) //打印9
	}(i)
	defer fmt.Printf("third i=%d\n", i) //defer后不是跟func,而直接跟一条执行语句,则相关变量在注册defer时被拷贝或计算
	return 5
}
third i=9
second i=9
first i=5
func defer_panic() {
    
    
	defer fmt.Println("111")
	n := 0
	defer func() {
    
    
		fmt.Println(2 / n)
		fmt.Println("222")
	}()
	fmt.Println("333")
}
333
111
panic

defer encounters a closure

package main

import "fmt"

func main() {
    
    
    var whatever [5]struct{
    
    }
    for i := range whatever {
    
    
        defer func() {
    
     fmt.Println(i) }()
    }
} 

Output result:

    4
    4
    4
    4
    4

In fact, go is very clear, let's take a look at what go spec says

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.

That is to say, the function is executed normally. Since the variable i used by the closure has become 4 during execution, the output is all 4.

defer f.Close

Everyone uses this very frequently, but go language programming gives an example that you may accidentally make a mistake.

package main

import "fmt"

type Test struct {
    
    
    name string
}

func (t *Test) Close() {
    
    
    fmt.Println(t.name, " closed")
}
func main() {
    
    
    ts := []Test{
    
    {
    
    "a"}, {
    
    "b"}, {
    
    "c"}}
    for _, t := range ts {
    
    
        defer t.Close()
    }
} 

Output result:

    c  closed
    c  closed
    c  closed

This output will not output cba as we expected, but output ccc

But according to the instructions in the previous go spec, it should output cba.

Then let's call it another way.

package main

import "fmt"

type Test struct {
    
    
    name string
}

func (t *Test) Close() {
    
    
    fmt.Println(t.name, " closed")
}
func Close(t Test) {
    
    
    t.Close()
}
func main() {
    
    
    ts := []Test{
    
    {
    
    "a"}, {
    
    "b"}, {
    
    "c"}}
    for _, t := range ts {
    
    
        defer Close(t)
    }
} 

Output result:

    c  closed
    b  closed
    a  closed

At this time, the output is cba

Of course, if you don’t want to write one more function, it’s also very simple. You can also output cba like the following

seemingly superfluous statement

package main

import "fmt"

type Test struct {
    
    
    name string
}

func (t *Test) Close() {
    
    
    fmt.Println(t.name, " closed")
}
func main() {
    
    
    ts := []Test{
    
    {
    
    "a"}, {
    
    "b"}, {
    
    "c"}}
    for _, t := range ts {
    
    
        t2 := t
        defer t2.Close()
    }
} 

Output result:

    c  closed
    b  closed
    a  closed

Through the above example, combined with

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.

This sentence. The following conclusions can be drawn:

When the statement after defer is executed, the parameters of the function call will be saved, but not executed. That is, a copy. But it doesn't say how to deal with the this pointer here in the struct. From this example, it can be seen that the go language does not treat the explicitly written this pointer as a parameter.

Multiple defer registrations are executed in FILO order (first in, last out). Even if an error occurs in a function or a deferred call, these calls will still be executed.

package main

func test(x int) {
    
    
    defer println("a")
    defer println("b")

    defer func() {
    
    
        println(100 / x) // div0 异常未被捕获,逐步往外传递,最终终止进程。
    }()

    defer println("c")
}

func main() {
    
    
    test(0)
} 

output result:

    c
    b
    a
    panic: runtime error: integer divide by zero

*Deferred call parameters are evaluated or copied at registration time, and can be read "lazily" using pointers or closures.

package main

func test() {
    
    
    x, y := 10, 20

    defer func(i int) {
    
    
        println("defer:", i, y) // y 闭包引用
    }(x) // x 被复制

    x += 10
    y += 100
    println("x =", x, "y =", y)
}

func main() {
    
    
    test()
}  

output result:

    x = 20 y = 120
    defer: 10 120

*Abuse of defer can cause performance problems, especially in a "big loop".

package main

import (
    "fmt"
    "sync"
    "time"
)

var lock sync.Mutex

func test() {
    
    
    lock.Lock()
    lock.Unlock()
}

func testdefer() {
    
    
    lock.Lock()
    defer lock.Unlock()
}

func main() {
    
    
    func() {
    
    
        t1 := time.Now()

        for i := 0; i < 10000; i++ {
    
    
            test()
        }
        elapsed := time.Since(t1)
        fmt.Println("test elapsed: ", elapsed)
    }()
    func() {
    
    
        t1 := time.Now()

        for i := 0; i < 10000; i++ {
    
    
            testdefer()
        }
        elapsed := time.Since(t1)
        fmt.Println("testdefer elapsed: ", elapsed)
    }()

}

output result:

    test elapsed:  223.162µs
    testdefer elapsed:  781.304µs

defer trap

defer 与 closure

package main

import (
    "errors"
    "fmt"
)

func foo(a, b int) (i int, err error) {
    
    
    defer fmt.Printf("first defer err %v\n", err)
    defer func(err error) {
    
     fmt.Printf("second defer err %v\n", err) }(err)
    defer func() {
    
     fmt.Printf("third defer err %v\n", err) }()
    if b == 0 {
    
    
        err = errors.New("divided by zero!")
        return
    }

    i = a / b
    return
}

func main() {
    
    
    foo(2, 0)
}  

Output result:

    third defer err divided by zero!
    second defer err <nil>
    first defer err <nil>

Explanation: If defer is not followed by a closure, we will not get the latest value when it is executed last.

defer and return

package main

import "fmt"

func foo() (i int) {
    
    

    i = 0
    defer func() {
    
    
        fmt.Println(i)
    }()

    return 2
}

func main() {
    
    
    foo()
}

Output result:

    2

Explanation: In a function with a named return value (here the named return value is i), the value of i has actually been reassigned to 2 when return 2 is executed. So the output of defer closure is 2 instead of 1.

defer nil function

package main

import (
    "fmt"
)

func test() {
    
    
    var run func() = nil
    defer run()
    fmt.Println("runs")
}

func main() {
    
    
    defer func() {
    
    
        if err := recover(); err != nil {
    
    
            fmt.Println(err)
        }
    }()
    test()
} 

Output result:

runs
runtime error: invalid memory address or nil pointer dereference

Explanation: The function named test runs to the end, then the defer function will be executed and a panic exception will be generated because the value is nil. However, it is worth noting that the statement of run() is fine, because it will not be called until the test function has finished running.

Using defer in the wrong place

Exception thrown when http.Get fails.

package main

import "net/http"

func do() error {
    
    
    res, err := http.Get("http://www.google.com")
    defer res.Body.Close()
    if err != nil {
    
    
        return err
    }

    // ..code...

    return nil
}

func main() {
    
    
    do()
} 

Output result:

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

Because here we did not check whether our request was successfully executed, when it failed, we accessed the empty variable res in the Body, so an exception will be thrown

solution

Always use defer after a successful resource allocation, which means in this case: use defer if and only if http.Get executes successfully

package main

import "net/http"

func do() error {
    
    
    res, err := http.Get("http://xxxxxxxxxx")
    if res != nil {
    
    
        defer res.Body.Close()
    }

    if err != nil {
    
    
        return err
    }

    // ..code...

    return nil
}

func main() {
    
    
    do()
} 

In the above code, when there is an error, err will be returned, otherwise when the entire function returns, res.Body will be closed.

Explanation: Here, you also need to check whether the value of res is nil, which is a warning in http.Get. Normally, when an error occurs, the returned content should be empty and the error will be returned, but when you get a redirection error, the value of res will not be nil, but it will return the error. The above code guarantees that the Body will be closed no matter what, if you do not intend to use the data in it, then you also need to discard the received data.

do not check for errors

Here, f.Close() may return an error, but this error will be ignored by us

package main

import "os"

func do() error {
    
    
    f, err := os.Open("book.txt")
    if err != nil {
    
    
        return err
    }

    if f != nil {
    
    
        defer f.Close()
    }

    // ..code...

    return nil
}

func main() {
    
    
    do()
}  

improve

package main

import "os"

func do() error {
    
    
    f, err := os.Open("book.txt")
    if err != nil {
    
    
        return err
    }

    if f != nil {
    
    
        defer func() {
    
    
            if err := f.Close(); err != nil {
    
    
                // log etc
            }
        }()
    }

    // ..code...

    return nil
}

func main() {
    
    
    do()
} 

improve it

Errors within defer are returned via named return variables.

package main

import "os"

func do() (err error) {
    
    
    f, err := os.Open("book.txt")
    if err != nil {
    
    
        return err
    }

    if f != nil {
    
    
        defer func() {
    
    
            if ferr := f.Close(); ferr != nil {
    
    
                err = ferr
            }
        }()
    }

    // ..code...

    return nil
}

func main() {
    
    
    do()
} 

release the same resource

If you try to release different resources with the same variable, this operation may not work properly.

package main

import (
    "fmt"
    "os"
)

func do() error {
    
    
    f, err := os.Open("book.txt")
    if err != nil {
    
    
        return err
    }
    if f != nil {
    
    
        defer func() {
    
    
            if err := f.Close(); err != nil {
    
    
                fmt.Printf("defer close book.txt err %v\n", err)
            }
        }()
    }

    // ..code...

    f, err = os.Open("another-book.txt")
    if err != nil {
    
    
        return err
    }
    if f != nil {
    
    
        defer func() {
    
    
            if err := f.Close(); err != nil {
    
    
                fmt.Printf("defer close another-book.txt err %v\n", err)
            }
        }()
    }

    return nil
}

func main() {
    
    
    do()
} 

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

When the deferred function executes, only the last variable will be used, so the f variable will become the last resource (another-book.txt). And both defers will close this resource as the last resource

solution:

package main

import (
    "fmt"
    "io"
    "os"
)

func do() error {
    
    
    f, err := os.Open("book.txt")
    if err != nil {
    
    
        return err
    }
    if f != nil {
    
    
        defer func(f io.Closer) {
    
    
            if err := f.Close(); err != nil {
    
    
                fmt.Printf("defer close book.txt err %v\n", err)
            }
        }(f)
    }

    // ..code...

    f, err = os.Open("another-book.txt")
    if err != nil {
    
    
        return err
    }
    if f != nil {
    
    
        defer func(f io.Closer) {
    
    
            if err := f.Close(); err != nil {
    
    
                fmt.Printf("defer close another-book.txt err %v\n", err)
            }
        }(f)
    }

    return nil
}

func main() {
    
    
    do()
} 

Guess you like

Origin blog.csdn.net/m0_52896752/article/details/130191669