A detailed example of the delay function defer in Go

  • 2020-06-07 04:38:25
  • OfStack

preface

Everyone knows that defer is very powerful and convenient for resource management, but there are pitfalls if it doesn't work. In Go, the delay function defer ACTS as try... catch is easy to use, but in practice, many gopher don't really understand the order of execution between defer, return, return value, and panic, and they fall into the trap. Without further ado, let's start with the detailed introduction.

Let's run the following two pieces of code:

A. Anonymous return values


package main

import (
 "fmt"
)

func main() {
 fmt.Println("a return:", a()) //  The print result is  a return: 0
}

func a() int {
 var i int
 defer func() {
  i++
  fmt.Println("a defer2:", i) //  The print result is  a defer2: 2
 }()
 defer func() {
  i++
  fmt.Println("a defer1:", i) //  The print result is  a defer1: 1
 }()
 return i
}

B. Case of named return value


package main

import (
 "fmt"
)

func main() {
 fmt.Println("b return:", b()) //  The print result is  b return: 2
}

func b() (i int) {
 defer func() {
  i++
  fmt.Println("b defer2:", i) //  The print result is  b defer2: 2
 }()
 defer func() {
  i++
  fmt.Println("b defer1:", i) //  The print result is  b defer1: 1
 }()
 return i //  Or directly  return  The effect is the same 
}

To help you understand why, assume the conclusion (which is correct) :

The order of execution of multiple defer is "lifO/FIFO"; All functions will check the existence of defer statement before executing RET return instruction. If so, call defer statement in reverse order to finish the work before exiting the return. Anonymous return value is declared during the execution of return, while named return value is declared at the same time of function declaration. Therefore, in defer statement, only named return value can be accessed, but anonymous return value cannot be directly accessed. return should actually consist of two steps: The first step is to assign a value to the return value (assign it directly if it is named, declare it first and then assign it if it is anonymous); The second step is to call the RET return instruction and pass in the return value, while RET will check whether defer exists. If it exists, it will first insert the defer statement in reverse order, and finally RET will exit the function with the return value.

Therefore, the & # 8205; The & # 8205; The execution order of defer, return and return value 3 should be: return assigns the return value first; defer then begins to perform 1 of the finishing touches; Finally, the RET instruction carries the return value to exit the function.

How to explain the difference between the two results:

It's easy to see why the results in the two sections above are different.

a()int The return value of the function is not named in advance, it is derived from the assignment of other variables, which are also modified in defer (defer does not have direct access to the return value at all), so the return value is not modified when the function exits. b()(i int) The return value of the function is called ahead of time so that defer can access the return value, so that after return assigns the return value i, defer calls the return value i and modifies it, so that the return value of return after calling RET exits the function will be the modified value of defer.

C. Now let's look at the third example to verify the above conclusion:


package main

import (
 "fmt"
)

func main() {
 c:=c()
 fmt.Println("c return:", *c, c) //  The print result is  c return: 2 0xc082008340
}

func c() *int {
 var i int
 defer func() {
  i++
  fmt.Println("c defer2:", i, &i) //  The print result is  c defer2: 2 0xc082008340
 }()
 defer func() {
  i++
  fmt.Println("c defer1:", i, &i) //  The print result is  c defer1: 1 0xc082008340
 }()
 return &i
}

although c()int The return value of is not declared in advance, but due to c()int After return assigns the address of the variable i to the return value, defer changes the actual memory value of i again. Therefore, when return calls RET to exit the function, the return value is still the original pointer address, but the actual memory value it points to has been successfully modified.

That is, our hypothesis is correct!

D. In addition, when defer is declared, the value of the determined parameter is calculated first, and defer only postpones its execution of its function body.


package main

import (
 "fmt"
 "time"
)

func main() {
 defer P(time.Now())
 time.Sleep(5e9)
 fmt.Println("main ", time.Now())
}

func P(t time.Time) {
 fmt.Println("defer", t)
 fmt.Println("P ", time.Now())
}

//  Output results: 
// main 2017-08-01 14:59:47.547597041 +0800 CST
// defer 2017-08-01 14:59:42.545136374 +0800 CST
// P  2017-08-01 14:59:47.548833586 +0800 CST

E. Scope of defer

defer is only valid for the current corollary (main can be considered the master corollary); When panic occurs for any 1 (main) coroutine, defer declared before panic in the current coroutine is executed. In the (main) coroutine in which panic occurs, if there are no defer calls recover() Restore and the entire process crashes after the last declared defer is executed. Take the initiative to call os.Exit(int) When exiting the process, defer will no longer be executed.

package main

import (
 "errors"
 "fmt"
 "time"
 // "os"
)

func main() {
 e := errors.New("error")
 fmt.Println(e)
 //  ( 3 ) panic(e) // defer  Do not perform 
 //  ( 4 ) os.Exit(1) // defer  Do not perform 
 defer fmt.Println("defer")
 //  ( 1 ) go func() { panic(e) }() //  Can lead to  defer  Do not perform 
 //  ( 2 ) panic(e) // defer  Will perform 
 time.Sleep(1e9)
 fmt.Println("over.")
 //  ( 5 ) os.Exit(1) // defer  Do not perform 
}

F. defer expressions are invoked in the order in which they are executed in a first-in, last-out fashion

defer expressions are put into a stack like (stack) structure, so the order of calls is last in, last out/last in, first out.

The following code outputs 4321 instead of 1234.


package main

import (
 "fmt"
)

func main() {
 defer fmt.Print(1)
 defer fmt.Print(2)
 defer fmt.Print(3)
 defer fmt.Print(4)
}

conclusion


Related articles: