1. Introduction
defer executes the passed function before the current function returns. It is often used to close file descriptors, close database connections, and unlock resources.
Understanding this sentence mainly involves three aspects:
- Current function
- Execute before returning, of course the function may not return a value
- The function passed in, that is, the defer key value is followed by a function, including ordinary functions such as (), or anonymous function func()
1.1 Use scenarios
The most common scenario for using defer is to complete some finishing work after the function call is finished, such as rolling back a database transaction in defer:
func createPost(db *) error { tx := () // Used to roll back database events defer () if err := (&Post{Author: "Draveness"}).Error; err != nil { return err } return ().Error }
When using database transactions, we can use the above code to call Rollback immediately after creating the transaction to ensure that the transaction will definitely rollback. Even if the transaction is really executed successfully, then calling () and then executing () will not affect the committed transaction.
1.2 Notes
usedefer
There are two common problems that will be encountered. Here we will introduce the specific scenarios and analyze the design principles behind these two phenomena:
The time of calling the defer keyword and how the execution order is determined when multiple calls to defer are called. The defer keyword is pre-calculated when passing parameters by passing values, resulting in the result that it does not meet the expected results.
Scope
The function passed to the defer keyword will run before the function returns.
Suppose we call the defer keyword multiple times in the for loop:
package main import "fmt" func main() { for i := 0; i < 5; i++ { // FILO, first in and then out, the keyword defer that appears first will be pushed into the bottom of the stack, and will be finally removed and executed defer (i) } }
#run
$ go run
4
3
2
1
0
Running the above code will execute all expressions passing in the defer keyword in reverse order. Because (4) is passed in the last call to defer, this code will print 4 first. We can use the following simple example to enhance our understanding of the execution timing of defer:
package main import "fmt" func main() { // Code block { defer ("defer runs") ("block ends") } ("main ends") }
#Output
$ go run
block ends
main ends
defer runs
From the output of the above code, we will find that the function passed in defer is not executed when exiting the scope of the code block, it will only be called before the current function and method return.
Pre-calculated parameters
All function calls in Go are passed values.
Although defer is a keyword, it also inherits this feature. Suppose we want to calculate the time when the main function runs, we might write the following code:
package main import ( "fmt" "time" ) func main() { startedAt := () // I mistakenly think that startingAt will pass the argument to the function in the defer statement afterward defer ((startedAt)) () }
#Output
$ go run
0s
The above code run results do not meet our expectations. What is the reason behind this phenomenon?
After analysis (or using debug method), we will find:
Calling the defer keyword will immediately copy the external parameters referenced in the function.
So the result of (startedAt) is not calculated before the main function exits, but is calculated when the defer keyword is called, which ultimately results in the above code outputting 0s.
The solution to this problem is very simple. We just need to pass anonymous function to the defer keyword:
package main import ( "fmt" "time" ) func main() { startedAt := () // Using anonymous functions, passing a pointer to the function defer func() { ((startedAt)) }() () }
#Output
$ go run
$ 1.0056135s
2. defer data structure
The corresponding data structure of the defer keyword in the Go language source code:
type _defer struct { siz int32 started bool openDefer bool sp uintptr pc uintptr fn *funcval _panic *_panic link *_defer }
A brief introduction to several fields in the runtime._defer structure:
- siz is the memory size of the parameters and results;
- sp and pc represent stack pointers and caller program counters, respectively;
- fn is a function passed in the defer keyword;
- _panic is the structure that triggers the delayed call, which may be empty;
- openDefer indicates whether the current defer has been optimized by open coding;
In addition to the above fields, runtime._defer also contains some fields used by the garbage collection mechanism. I will not explain too much here.
3. Execution mechanism
Heap allocation, stack allocation and open encoding are three ways to deal with the defer keyword.
- Early Go languages were allocated on the heap, but their performance was poor
- Go language introduces structures allocated on the stack in 1.13, reducing the additional overhead by 30%.
- In 1.14, an open encoding-based defer was introduced so that the additional overhead of the keyword is negligible
Don't make too many instructions on the heap allocation
3.1 Allocation on the stack
The defer keyword was optimized in 1.13. When the keyword is executed at most once in the function body, the structure will be assigned to the stack and called.
Apart from the allocation location, there is no essential difference between the allocation on-stack and the runtime._defer allocated on-heap, and this method can be applied to most scenarios. Compared with the runtime._defer allocated on-heap, this method can reduce the additional overhead of the defer keyword by ~30%.
3.2 Open coding
The defer keyword is implemented in 1.14 through Open Coded. This design uses code inline to optimize the extra overhead of defer key and introduces function data to funcdata to manage calls to panic 3. This optimization can drive the call overhead of defer from version 1.13~35ns
Lower to~6ns
about:
However, as a method to optimize the defer keyword, it is not enabled in all scenarios. Open encoding will only be enabled when the following conditions are met:
- The number of defers of the function is less than or equal to 8;
- The defer keyword of the function cannot be executed in a loop
- The result of the return statement of the function and the defer statement is less than or equal to 15.
4. Reference
/golang/docs/part2-foundation/ch05-keyword/golang-defer/
This is the article about the precautions for using defer in Go. For more related content on using defer in Go, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!