Golang efficient practice of defer, panic, recover practice

 Foreword

We know that is the way to handle exceptions Golang error is returned, the caller then take a different processing logic based on the value of the error. However, if the program triggers other serious abnormalities, such as array bounds, we will direct the program to crash. Golang Is there an unusual capture and recovery mechanism? This article is talk of panic and recover. Which recover to tie defer use to play the effect.

Defer

Defer statement will be a function into a list (with a stack represents actually more accurate), the function of the list will be executed when the function returns surround defer. defer commonly used to simplify the function of a variety of clean-up actions, such as closing files, etc. actions to unlock the release of resources. For example, the following function to open two files, a file copy from the contents of a file to another:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}

This code works, but there is a bug. If the call os.Create fails, the function will return directly, and did not close srcName file. The fix is ​​simple, you can call src.Close placed in front of the second return statement. But when more of our branch procedures, that is to say when the function There are several other return statement, we need to be coupled with action before the close of each branch return. This makes clean-up resources are very tedious and easy to miss. So Golang defer the introduction of the statement:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

Behind every successful application resources are plus defer automatically clean up, no matter how many of the functions return, resources will be properly released, such as the above example the file will be closed.

Close defer statement, there are three simple rules:

1.defer function will save the value of the parameter when the push, not the value at the time of execution.

func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}

For example, the example, the variable i may be saved when it defer, defer function so when the value of i is performed before copying the value 0. Even if the back of i becomes 1, is not affected.

Order 2.defer function call is LIFO.

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}

Function output 3210

3.defer functions can be read and re-assignment naming function's return parameters.

func c() (i int) {
    defer func() { i++ }()
    return 1
}

In this case, defer naming function return values ​​when the function returns 1 plus i have been operating, so the function return value is 2. You may be in doubt, rule 1 does not mean that will be saved when the value of i defer it? I saved is 0, also 1 plus 1 after that operation ah. Here's the beauty of closures, the value of i will be immediately saved, but i saved is a reference, it can also be understood as a guideline. When the actual operation performed plus 1, return the value of i is set to 1 in fact, the value of the defer execution operation i is incremented by 1 will become a 2.

Panic

Panic is a function of the built-in control flow stopped. Equivalent to other programming languages ​​Throws operation. When the panic function F is called, execution will be stopped F, F in the panic defer defined above operation will be performed, then the function returns F. For the caller, the call F behaves like calling panic (if F internal function did not panic recover lost). If you have not caught the panic, the equivalent of a layer of panic, the program will crash. panic can be called directly, can also be a cause errors when the program is running, such as array bounds.

Recover

Recover is a built-in function to recover from panic. Recover only play a real role in the function defer inside. If it is a normal situation (no panic occurred), call recover will return nil and have no effect. If the current goroutine panic, make a call to recover will capture the value of panic, and resume normal execution.

For example the following example:

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

Function accepts parameters g i, if i is greater than the trigger panic 3, otherwise i are incremented. Defer function f and the function which called recover recover the value of the print (if non-nil).

Program will output:

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.

Panic and recover any type of acceptable values, defined as interface {}:

func panic(v interface{})

func recover() interface{}

So the work mode is equivalent to:

panic(value)->recover()->value

value passed to panic finally captured by recover.

 

In addition defer lock can be used together to ensure the release of the lock, for example:

mu.Lock()

Defer mu.Unlock()

Note that this will extend the lock release time (need to wait until the function return).

 

Some examples easy to step pits

By the above description, we have a clearer understanding of defer, panic and recover, to deepen under the impression by the following examples of some real easy to step on the pit.

In use inside the loop defer

Do not use the defer cycle in which, unless you're really sure workflow defer, for example:

Only when the function returns defer the function will be executed, if the function defer defined in the for loop will continue to push, could lead to explosive stack program exception.

Solution 1: defer to move out of a loop

Solution 2: constructing a new layer of wrapping function defer

defer method

No pointer of the situation:

type Car struct {
  model string
}
func (c Car) PrintModel() {
  fmt.Println(c.model)
}
func main() {
  c := Car{model: "DeLorean DMC-12"}
  defer c.PrintModel()
  c.model = "Chevrolet Impala"
}

Program output DeLorean DMC-12. According to our speaking in front of content, defer the time reference and a copy function will be saved, so the change will not affect the operation of the rear defer the value c.model.

There are pointers situation:

Car PrintModel () method is defined to:

func (c *Car) PrintModel() {
  fmt.Println(c.model)
}

Program will output Chevrolet Impala. Although these defer save up functions and parameters, but due to the value of the parameter itself is directed, random changes will affect the behavior of the latter to defer the function.

Similarly examples are:

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

Program will output:

3
3
3

Since the outer closure anonymous reference variable corresponding to function pointer references are to give the address of a variable, the actual change to defer actually executed, the contents of pointer has occurred:

the solution:

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

or:

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

Program output:

2
1
0

Not used here closures context reference characteristic, parameter copy function is passed serious, so there is no problem.

defer the error modification function return value

package main

import (
    "errors"
    "fmt"
)

func main() {
    {
        err := release()
        fmt.Println(err)
    }

    {
        err := correctRelease()
        fmt.Println(err)
    }
}

func release() error {
    defer func() error {
        return errors.New("error")
    }()

    return nil
}

func correctRelease() (err error) {
    defer func() {
        err = errors.New("error")
    }()
    return nil
}

release function value error and defer the return will not be returned because the anonymous return value before defer execution has been declared good and copied to nil. correctRelease can modify the function return value because of the nature of the closures, the defer the actual return value err err address references pointing to the same variable. defer modify the program returns error value is generally used in the mix and recover in the case of abuse defer belongs, in fact, the value of the error function can be modified directly in the return of the program, not defer.

to sum up

This paper introduces the principle and usage defer, panic and recover, and at last gives some practical advice on practical application, not to abuse defer, defer attention with some of the characteristics of the closures.

reference

https://blog.golang.org/defer-panic-and-recover

https://blog.learngoprogramming.com/gotchas-of-defer-in-go-1-8d070894cb01

https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa

https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff

https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1

 

 

 

 

 

 

 

Guess you like

Origin www.cnblogs.com/makelu/p/11226974.html