The defer keyword is used to mark the last Go statement to be executed. It is usually used to free resources, close connections, and so on, and is called before the function returns. Defer is executed in the first out order.
Defer and parameter resolution
When defer was declared, the parameters executed in defer were resolved by the implementation instead of after return. After I is defined in the following code, I is then printed by defer. The subsequent operations increment I and the program prints 0 instead of 1 at the end.
func main(a) {
var i int
defer fmt.Println(i)
i++
}
// Prints the result: 0
Copy the code
We changed the code slightly and executed fmt.println (I) in the anonymous function func(), which prints 1. This is because the anonymous function deferred executed kept references to external variables. After I ++ was executed, Defer gets the external variable I when it executes, so the output is 1.
func main(a) {
var i int
defer func(a) {
fmt.Println(i)
}()
i++
}
// Print the result: 1
Copy the code
To prevent the closure problems above, we can pass the external variable I into the anonymous function defer during execution. In this case, the value of I will be copied. The variable I copied during the execution of defer will not be affected by the external variable I ++.
func main(a) {
var i int
defer func(i int) {
fmt.Println(i)
}(i)
i++
}
// Prints the result: 0
Copy the code
For this reason, when we call defer to close the connection or release the resource, the correct thing to do would be to defer the defer execution after Err, and return if ERR is not nil, such as sending an HTTP request in the following example, The connection was closed by defer, but deferred was written before ERR, which directly caused panic in the program because the RESP object was nil when the GET request was called. Since the arguments in defer have been resolved to nil in real time at this point, calling the Close method through a nil object naturally causes panic.
// Error 1
func main(a) {
resp, err := http.Get("")
defer resp.Body.Close()
iferr ! =nil {
fmt.Println(err.Error())
return}}// Error 2: Ignore err, which also causes panic
func main(a) {
resp, _ := http.Get("")
defer resp.Body.Close() // Error 2
}
Copy the code
The correct thing to do is to defer after err and exit if you get an error so that defer isn’t nil when resp.body.close is executed.
func main(a) {
resp, err := http.Get("")
iferr ! =nil {
fmt.Println(err.Error())
return
}
defer resp.Body.Close() // The right thing to do
}
Copy the code
Defer with anonymous/named return values
If we were to modify the function name return value in defer, we would need to be very careful. Let’s look at the output from a few simple examples:
func main(a) {
fmt.Println(f1())
fmt.Println(f2())
fmt.Println(f3())
fmt.Println(f4())
fmt.Println(f5())
}
func f1(a) (res int) {
res = 5
defer func(a) {
res = res + 5} ()return
}
func f2(a) int {
res := 5
defer func(a) {
res = res + 5} ()return res
}
func f3(a) (res int) {
t := 5
defer func(a) {
t = t + 5} ()return t
}
func f4(a) (res int) {
res = 5
defer func(res int) {
res = res + 5
}(res)
return
}
func f5(a) (res int) {
res = 5
defer func(a) {
res = res + 5} ()return 10
}
// Print the result:
10
5
5
5
15
Copy the code
To understand the output results of the above is the key to return XXX this statement, this statement is not an atomic operation, after compiling can be decomposed into the following 3 instruction: first step: the function return value = XXX, if the function return value for anonymous format, so will use a temporary variable to hold the return value The second step: Step 3: The RET directive returns the value so we can unpack the five cases as follows:
// name returns a value
func f1(a) (res int) {
// First step: determine that the function is a named return value, so use the named return value variable to report the assigned data error
res = 5
// Step 1: Execute the defer function
func(a) {
res = res + 5} ()// Step 3: Return the named return value variable
return res // This return does not break into 3 steps, just to show that it returns the res variable
}
// Return an anonymous value
func f2(a) int {
res := 5
// Step 1: Determine that the function is an anonymous return value, so use a temporary variable to hold the return value
tmp := res
// Step 2: Execute the defer function
func(a) {
res = res + 5} ()// Step 3: Return the value of the temporary variable
return tmp // This return does not break into 3 steps, just to show that the TMP variable is returned
}
func f3(a) (res int) {
t := 5
// Step 1: Determine that the function is a named return value, so use a named return variable to hold the assigned data
res = t
// Step 2: Execute the defer method
func(a) {
t = t + 5} ()// Step 3: Return the named return value variable
return res // This return does not break into 3 steps, just to show that it returns the res variable
}
func f4(a) (res int) {
// First step: determine that the function is a named return value, so use the named return value variable to report the assigned data error
res = 5
// Step 2: Execute the defer method
func(res int) {
res = res + 5 // the outer res is not the same variable
}(res)
// Step 3: Return the named return value variable
return res // This return does not break into 3 steps, just to show that it returns the res variable
}
func f5(a) (res int) {
res = 5
// First step: determine that the function is a named return value, so use the named return value variable to report the assigned data error
res = 10
// Step 2: Execute the defer function
func(a) {
res = res + 5} ()// Step 3: Return the named return value variable
return res // This return does not break into 3 steps, just to show that it returns the res variable
}
Copy the code
Through the case analysis above, we can see the defer is can modify the return value function return values, famous in the first step to deal with this problem we need to judgment is famous returns or anonymous returns function, this function will be decided finally return which variables are used, if it is a famous return function, You do not need to create a new variable to save the return value. If an anonymous return function returns a variable, a temporary variable will be created to save the return value before the return. If we are familiar with the above rule, the following example can be analyzed quickly:
func main(a) {
fmt.Println("The return value n is:", demo01()) / / output 11
fmt.Println("The return value n is:", demo02()) / / output 6
fmt.Println("The return value n is:", demo03()) / / output 5
fmt.Println("The return value n is:", demo04()) / / output 1
}
func demo01(a) int {
n := 10
defer func(a) {
n++
fmt.Println("Defer1 values are:", n) / / output 12} ()defer func(a) {
n++
fmt.Println("Defer2 has the value:", n) / / output 13
}()
n++
return n
}
func demo02(a) (n int) {
n = 10
defer func(a) {
n++
fmt.Println("Defer n is:", n) / / output 6} ()return 5
}
func demo03(a) (i int) {
n := 10
defer func(a) {
n++
fmt.Println("Defer n is:", n) / / output 11} ()return 5
}
func demo04(a) (i int) {
defer func(n int) {
n++
fmt.Println("Defer n is:", n) / / output 1
}(i)
i++
return i
}
Copy the code
Defer to recover
For the sake of syntax simplicity, Go does not support the traditional try-catch-finally syntax to handle exceptions. However, when we encounter exceptions such as null pointer assignment and panic, we can catch exceptions by defer and recover to prevent the program from leaving. When panic occurs, the program is thrown out of the call stack until recovery is encountered. If recovery does not catch it, the main program will be violently terminated. There are two points to note here:
- Recovery only works in this Goroutine;
- The place where the program recovers is the place where panic is recovered. After the method that captured Recover is executed, the subsequent code of the method will not be executed, but the function where Recover is directly exited.
The first point is actually very important and one that we tend to overlook. When we write code, we often use Groutine to create a coroutine to run asynchronously, but sometimes it’s easy to overlook whether an exception occurs during the groutine operation. If we don’t use RECOVER to catch the exception, Gin starts an httpServer as an example:
func main(a) {
r := gin.Default()
r.GET("/get".func(c *gin.Context) {
go func(a) {
var m map[string]int
m["1"] = 1
}()
c.JSON(http.StatusOK, gin.H{
"succes": 0,
})
})
r.Run()
}
Copy the code
The above example is very simple, but if we call the /get request, the whole process will exit, because we did not use recover to catch a panic in the /get request. Default (” Recover Middleware “) does not contain Panic Recover. Recover will only capture Panic during the current Groutine lifecycle. If we change the code:
func main(a) {
r := gin.Default()
r.GET("/get".func(c *gin.Context) {
var m map[string]int
m["1"] = 1
c.JSON(http.StatusOK, gin.H{
"succes": 0,
})
})
r.Run()
}
Copy the code
The request /get routing program will report panic, but the entire process will not exit because panic and Gin’s Recovery() Middlerware are in the same groutine and will eventually be caught. Therefore, it reminds us that we must be responsible for the Groutine we created, and must deal with panic properly. Therefore, the recommended method is to refer to SafeGo method instead of directly using go keyword to create Groutine.
func Recovery(ctx context.Context) {
e := recover(a)if e == nil {
return
}
if ctx == nil {
ctx = context.Background()
}
err := fmt.Errorf("%v", e)
panicLoc := identifyPanicLoc()
Logger(ctx).WithErrTag("ee_lark_common_panic").WithError(err).Errorf(
"catch panic!!! panic location: %v \nstacktrace:\n%s", panicLoc, debug.Stack())
}
func SafeGo(fn func(a)) {
go func(a) {
defer Recovery(nil)
fn()
}()
}
func SafeGoWithCtx(ctx context.Context, fn func(a)) {
go func(a) {
defer Recovery(ctx)
fn()
}()
}
Copy the code
In addition to meeting the condition that Recover and Panic are in the same groutine, it also needs to meet the requirement that the function that executes defer has a displayed inclusion relationship with the function where Panic is located. That is, defer can only be captured in the current Panic function or in the parent of the current Panic function, and it is explicitly captured by defer. We can look at the following two examples, in both cases, panic information is not captured.
func main(a) {
Recover() // Error 1: Panic is not displayed in the parent function of panic
testRecover()
}
func testRecover(a) {
Recover() // Error 2: the call defer shown in the current panic method is not satisfied
panic("test")}func Recover(a) {
defer func(a) {
if err := recover(a); err ! =nil {
fmt.Println(err)
}
}()
}
Copy the code
We need to make the following modifications: put the keyword defer displayed in advance into the sibling or parent function of Panic to call it:
func main(a) {
defer Recover() Correct usage 1: the call defer shown in the parent function can catch panic in the child function
testRecover()
}
func testRecover(a) {
// defer Recover() // Correct use 2: the call to defer shown in the current function can catch panic in the current function
panic("test")}func Recover(a) {
if err := recover(a); err ! =nil {
fmt.Println(err)
}
}
Copy the code
Two more examples are used to illustrate the entire code execution flow after panic is recovered. Case 1: Inner Recovery
func f1(a) {
fmt.Println("f1 start")
fmt.Println("f1 end")}func f2(a) {
defer func(a) {
if err := recover(a); err ! =nil {
fmt.Println(err)
}
}()
fmt.Println("f2 start")
panic("f2 panic") // The code after panic will not be executed
fmt.Println("f2 end")}func f3(a) {
fmt.Println("f3 start")
fmt.Println("f3 end")}func main(a) { // F2 is terminated by Panic and recovery() is returned, f3 is executed
f1()
f2()
f3()
}
/ / output:
// f1 start
// f1 end
// f2 start
// f2 panic
// f3 start
// f3 end
Copy the code
Case 2: Recovery in outer layer
func f1(a) {
fmt.Println("f1 start")
fmt.Println("f1 end")}func f2(a) {
fmt.Println("f2 start")
panic("f2 panic")
fmt.Println("f2 end")}func f3(a) {
fmt.Println("f3 start")
fmt.Println("f3 end")}func main(a) { // If panic is thrown to main, main will terminate and F3 will not be executed
defer func(a) {
if err := recover(a); err ! =nil {
fmt.Println(err)
}
}()
f1()
f2() F3 will not be executed after F2 sends panic
f3()
}
/ / output:
// f1 start
// f1 end
// f2 start
// f2 panic
Copy the code
Performance issues with defer
Deferproc and Runtime.deferreturn are the internal runtime functions we used to call too many times when we used defer: In the DeferProc phase (registering deferred calls), you also get/pass in the target function address, function parameters, and so on. In the DeferReturn phase, you need to insert the method call at the end of the function call, and jump to any functions that have been deferred using Runtime ·jmpdefer for subsequent calls. So if you have performance requirements for hot code, avoid using defer wherever you can. Prior to GO1.13, there was a significant performance cost to defer. However, the official point is that after optimization, the efficiency of GO1.13 is 30% higher than before. The following is the pressure test under GO1.14.12, and it can be seen that the performance of using defer is still much worse than that of not using defer.
func BenchmarkDeferFunc(b *testing.B) {
for n := 0; n < b.N; n++ {
DeferFunc("a"."b")}}func BenchmarkNotDeferFunc(b *testing.B) {
for n := 0; n < b.N; n++ {
NotDeferFunc("a"."b")}}func DeferFunc(key, value string) {
defer func(a) {
_ = key + value
}()
}
func NotDeferFunc(key, value string) {
_ = key + value
}
// Pressure test results
go test -v -bench=. -benchtime=10s
goos: darwin
goarch: amd64
BenchmarkDeferFunc
BenchmarkDeferFunc- 8 - 643887152 19.2 ns/op
BenchmarkNotDeferFunc
BenchmarkNotDeferFunc- 8 - 837066096 13.7 ns/op
Copy the code