When it comes to defer, you first need to understand its usage rules. The defer statement meets three principles:
defer rule
1. The parameters of the delay function have been determined when the defer function appears
func test1() { i := 0 defer fmt.Println(i) i++ return } => The print result is 0
In the above experimental function 1, when defer appears, the value of the parameter i of the delay function fmt.Println has been determined, which is 0 at this time; Therefore, even if the value of i is updated after defer, the final result printed by defer is still 0
func test2(){ i := map[string]int64{ "1": 1, "2": 2, } defer fmt.Println(i) i["3"] = 3 return } => The print result is map[1:1, 2:2, 3:3]
In the above experimental function 2, because i is the variable map of reference type at this time, the reference of map is determined when the defer statement appears. After the map is modified, the defer will still perceive the change during execution, that is:
For pointer type parameters, the rule still applies, except that the parameter of the delay function is an address value. In this case, the modification of variables in the statement after defer may affect the delay function.
2. The delay function is executed in the order of last in first out, that is, the defer that appears first is executed last
The definition of the defer function is similar to the operation of entering the stack. It performs the operation similar to the operation of exiting the stack, last in first out;
Each goroutine maintains a defer linked list for executing defer functions
3. The delay function may operate on the named return value of the main function
This principle involves the process of function return. First of all, it should be clear that the return function is not an atomic operation
func test3() (i int64) { i = 0 defer func() { i++ }() return i } fmt.Println(test3()) => 1
The function return is divided into two steps. Take return i in test3 function as an example:
- The first step is to store the value of i in the stack as the return value
- The second part is to execute the jump program execution instruction ret
The execution of the defer function is just after the first step and before the second step, so defer can modify the return value of the named function
Implementation principle of defer
Underlying data structure
type _defer struct { siz int32 // defer function parameter size started bool heap bool openDefer bool sp uintptr // sp calling the function pc uintptr // pc calling function fn *funcval // defer delay function pointer _panic *_panic // Triggered panic information link *_defer // defer function linked list }
Def creation
The creation process of defer is defined in the deferproc() function in runtime/panic.go
// @titile: deferproc // @description: create a defer function // @param: // siz: int32. Parameter size, bytes // fn: *funcval. Function pointer func deferproc(siz int32, fn *funcval) { // Get current goroutine gp := getg() ... // getcallersp returns the stack pointer (SP) of its caller's caller // Gets the stack pointer of the calling function (the function calling defer) sp := getcallersp() argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn) // getcallerpc returns the program counter (PC) of its caller's caller // Gets the program counter of the calling function callerpc := getcallerpc() // Create a new_ defer object d := newdefer(siz) if d._panic != nil { throw("deferproc: d.panic != nil after newdefer") } // This two-step linked list operation will create a new_ Put the defer object into the chain header of goroutine d.link = gp._defer gp._defer = d d.fn = fn d.pc = callerpc d.sp = sp // Handle the parameters and copy the parameters of the specified size to_ Parameter area corresponding to defer object switch siz { case 0: case sys.PtrSize: *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp)) default: memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz)) } return0() }
Implementation of defer
The execution process of defer is defined in the deferreturn() function in runtime/panic.go
// @title: deferreturn // @description: defer execution function (some codes are the same as deferproc, so there is no comment) // @param: // arg0: uintptr. Address to store delay function parameters func deferreturn(arg0 uintptr) { gp := getg() d := gp._defer if d == nil { return } sp := getcallersp() if d.sp != sp { return } // This parameter is not familiar. Skip it temporarily if d.openDefer { ... } // Copy the parameters of the delay function to args0 switch d.siz { case 0: // Do nothing. case sys.PtrSize: *(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d)) default: memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz)) } fn := d.fn d.fn = nil // Take the current defer object out of the linked list, and goroutine directly points to the next object that the current defer points to_ Defer object // Create_ Put the defer into the chain header, take it out from the header, and realize the last in first out stack model gp._defer = d.link // The Freeder function mainly clears the current_ Parameter of the defer object to ensure that it can no longer be executed freedefer(d) // Execution delay function jmpdefer(fn, uintptr(unsafe.Pointer(&arg0))) }
From the creation and execution process of defer, we can summarize the following points:
- The defer function is registered in the current goroutine_ The last in first out stack model is implemented through a linked list
- It also shows that the scope of the defer function is in the current goroutine and cannot be executed across goroutines