go learning notes: Interpretation of defer delay function

Keywords: Go Programming

There is a defer keyword in Go language, which is often used to implement delay function to ensure the final execution of key code. As the saying goes, "you can be prepared before it rains."

Delay function is such a mechanism. No matter whether the program returns normally or reports an error abnormally, as long as there is a delay function, it can ensure the final execution of this part of critical logic, so it is suitable to do some resource cleaning and other operations.

Going in and out in pairs has a beginning and an end

In daily development programming, some operations always appear in pairs. When they start, they end, when they open, they close, and some continuous dependencies.

Generally speaking, we need to control the closing statement, control the closing statement at the right place and time, and manually ensure that the whole program has a beginning and an end without missing the cleaning and closing operation.

The most common operation procedures for copying files are as follows:

  1. open the source file
srcFile, err := os.Open("fib.txt")
if err != nil {
    t.Error(err)
    return
}    
  1. Create target file
dstFile, err := os.Create("fib.txt.bak")
if err != nil {
    t.Error(err)
    return
}
  1. Copy source file to target file
io.Copy(dstFile, srcFile)
  1. close the target file
dstFile.Close()
srcFile.Close()
  1. close the source file
srcFile.Close()

It is worth noting that the operation of copying files should pay special attention to the operation sequence and do not forget to release resources, such as opening and closing first, etc.

func TestCopyFileWithoutDefer(t *testing.T) {
    srcFile, err := os.Open("fib.txt")
    if err != nil {
        t.Error(err)
        return
    }

    dstFile, err := os.Create("fib.txt.bak")
    if err != nil {
        t.Error(err)
        return
    }

    io.Copy(dstFile, srcFile)

    dstFile.Close()
    srcFile.Close()
}

"Snow dream technology station": the above code logic is still clear and simple, and may not forget to release resources to ensure the operation sequence, but if the logic code is more complex, then it will be difficult to achieve!

In order to simplify the logic of similar code, Go language introduces the defer keyword and creates the concept of "delay function".

  • File copy without defer
func TestCopyFileWithoutDefer(t *testing.T) {
    if srcFile, err := os.Open("fib.txt"); err != nil {
        t.Error(err)
        return
    } else {
        if dstFile,err := os.Create("fib.txt.bak");err != nil{
            t.Error(err)
            return
        }else{
            io.Copy(dstFile,srcFile)
    
            dstFile.Close()
            srcFile.Close()
        }
    }
}
  • File copy with defer
func TestCopyFileWithDefer(t *testing.T) {
    if srcFile, err := os.Open("fib.txt"); err != nil {
        t.Error(err)
        return
    } else {
        defer srcFile.Close()

        if dstFile, err := os.Create("fib.txt.bak"); err != nil {
            t.Error(err)
            return
        } else {
            defer dstFile.Close()

            io.Copy(dstFile, srcFile)
        }
    }
}

The above example code simply shows the basic usage of the defer keyword. The significant advantage is that Open/Close is a pair of operations, and the Close operation will not be forgotten because of the last write, and the delay time can be guaranteed normally when continuous dependency occurs.

In short, if there is a continuous dependency within a function, that is to say, the creation order is a - > b - > C and the destruction order is C - > b - > A. at this time, the def keyword is most suitable.

Lazy Gospel delay function

See the official documents for relevant statements Defer statements

If there is no defer function, the normal function runs normally:

func TestFuncWithoutDefer(t *testing.T) {
    // "Snow dream technology station": normal order
    t.Log("「Snow dream technology station」: Normal order")

    // 1 2
    t.Log(1)
    t.Log(2)
}

When the defer keyword is added to implement the delay, the original 1 is postponed to the next 2 instead of the previous 1 2 order.

func TestFuncWithDefer(t *testing.T) {
    // "Snow dream technology station": defer code is executed after normal sequence execution
    t.Log(" 「Snow dream technology station」: Only after the normal sequence is completed defer Code")

    // 2 1
    defer t.Log(1)
    t.Log(2)
}

If there are multiple defer keywords, the execution order can be imagined. The later, the earlier to execute, so as to ensure that resources are released in order of dependency.

func TestFuncWithMultipleDefer(t *testing.T) {
    // "Snow dream technology station": guess that the underlying data structure of defer may be stack.
    t.Log(" 「Snow dream technology station」: guess defer The underlying implementation data structure may be a stack,Advanced post production.")

    // 3 2 1
    defer t.Log(1)
    defer t.Log(2)
    t.Log(3)
}

I believe you have understood the execution order of multiple defer statements, so test it!

func TestFuncWithMultipleDeferOrder(t *testing.T) {
    // "Snow dream technology station": the underlying implementation data structure of defer is similar to the stack structure, which reverses and executes multiple defer statements in turn
    t.Log(" 「Snow dream technology station」: defer The underlying implementation data structure is similar to the stack structure,Performing multiple flashbacks in turn defer Sentence")

    // 2 3 1
    defer t.Log(1)
    t.Log(2)
    defer t.Log(3)
}

After a preliminary understanding of the use of defer delay function, we will interpret the relevant definitions in detail in combination with the document.

  • Original English document

A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns,either because the surrounding function executed a return statement,reached the end of its function body,or because the corresponding goroutine is panicking.

  • Chinese translation documents

The "defer" statement calls a function, and the execution of the function is delayed to the moment when the surrounding function returns. This is because the surrounding function executes a return statement, reaches the end of the function body, or the corresponding program is panicking.

Specifically, the execution time of delay function can be roughly divided into three situations:

Execute return for surrounding function

because the surrounding function executed a return statement

The t.Log(4) statement after return will not run naturally. The final output of the program is 3201, which indicates that the defer statement will execute in reverse order before the return of the surrounding function.

func funcWithMultipleDeferAndReturn() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    fmt.Println(3)
    return
    fmt.Println(4)
}

func TestFuncWithMultipleDeferAndReturn(t *testing.T) {
    // "Snow dream technology station": defer delay function will execute in reverse order before normal return of surrounding function.
    t.Log(" 「Snow dream technology station」: defer The delay function will be normal in the enclosing function return Execute in reverse order before.")

    // 3 2 1
    funcWithMultipleDeferAndReturn()
}

Surrounding function reaches function body

reached the end of its function body

The function body of the surrounding function runs to the end and executes multiple defer statements in reverse order, that is, output 3 first and then output 21.
The output of the final function is 3201, which means that the defer function can be executed before the end without return declaration.

func funcWithMultipleDeferAndEnd() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    fmt.Println(3)
}

func TestFuncWithMultipleDeferAndEnd(t *testing.T) {
    // "Snow dream technology station": defer delay function will execute in reverse order before the surrounding function reaches the end of function body.
    t.Log(" 「Snow dream technology station」: defer Delay functions are executed in reverse order before the enclosing function reaches the end of the function body.")

    // 3 2 1
    funcWithMultipleDeferAndEnd()
}

The current process is panicking

because the corresponding goroutine is panicking

In case of panic, the surrounding function will run the previously defined defer statement first, and the subsequent code of panic will not run if the program crashes because there is no special processing.

The final output of the function is 3201 panic, so it seems that the defer delay function is very dedicated to its duties. Although it is very flustered, it can ensure that the elderly, the sick and the disabled retreat first!

func funcWithMultipleDeferAndPanic() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    fmt.Println(3)
    panic("panic")
    fmt.Println(4)
}

func TestFuncWithMultipleDeferAndPanic(t *testing.T) {
    // "Snow dream technology station": defer delay function will execute in reverse order before panic of surrounding function panic.
    t.Log(" 「Snow dream technology station」: defer The delay function will surround the function panic Execute in reverse order before panic.")

    // 3 2 1
    funcWithMultipleDeferAndPanic()
}

By interpreting the definition of defer delay function and related examples, I believe it has been made clear what is defer delay function?

In a word, delay function is a kind of planning mechanism to prepare for the future. It helps developers to finish the work in time when programming programs, and make plans in advance to be ready to deal with various situations at any time.

  • When the surrounding function normally runs to the end of the function body, if there is a delay function, it will execute the delay function in reverse order.
  • When the return statement is ready to return to the caller during normal execution of surrounding functions, it will also be executed when there is a delay function, which also meets the needs of aftermath cleaning.
  • When the surrounding function runs abnormally, panic will occur in panic, the program will not forget to execute the delayed function, and the plan will play a role in advance.

So whether it's normal operation or abnormal operation, it's always right to make a plan in advance, which can ensure that there is no risk, so we might as well consider the defer function?

Delay function application scenario

Basically, delay function can be used for pairs of operations, especially when there is a dependency between the requested resources and before, the defer keyword should be used to simplify the processing logic.

Here are two common examples to illustrate the application scenario of delay function.

  • Open/Close

File operations generally involve opening and closing operations, especially copying operations between files in a strict order. Resource release operations can be satisfied only by following defer in the order of applying for resources.

func readFileWithDefer(filename string) ([]byte, error) {
    f, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer f.Close()
    return ioutil.ReadAll(f)
}
  • Lock/Unlock

Lock application and release is an important mechanism to ensure synchronization. When multiple lock resources need to be applied, there may be a dependency. Try the delay function!

var mu sync.Mutex
var m = make(map[string]int)
func lookupWithDefer(key string) int {
    mu.Lock()
    defer mu.Unlock()
    return m[key]
}

Summary and notice of next section

defer delay function is a mechanism to ensure the normal operation of key logic. If there are multiple delay functions, they will generally run in reverse order, similar to stack structure.

Generally speaking, there are three kinds of running time of delay function:

  • When the surrounding function encounters a return
func funcWithMultipleDeferAndReturn() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    fmt.Println(3)
    return
    fmt.Println(4)
}
  • At the end of function body of surrounding function
func funcWithMultipleDeferAndEnd() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    fmt.Println(3)
}
  • The current process is panicking
func funcWithMultipleDeferAndPanic() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    fmt.Println(3)
    panic("panic")
    fmt.Println(4)
}

This paper mainly introduces what the defer delay function is. By reading the official documents and matching the relevant codes, we know the delay function. However, there are some confusing points in the delay function.

The reader may wish to take a look at the following code and compare the conjecture in his mind with the actual running result. We will share it next time. Thank you for reading.

func deferFuncWithAnonymousReturnValue() int {
    var retVal int
    defer func() {
        retVal++
    }()
    return 0
}

func deferFuncWithNamedReturnValue() (retVal int) {
    defer func() {
        retVal++
    }()
    return 0
}

Extended reading of reference documents

If this article is helpful to you, you don't need to appreciate it or forward it. Just click the "like" message to encourage you!

Posted by blackthunder on Fri, 18 Oct 2019 03:06:33 -0700