The data structure

Defer data structure defined in $GOROOT/SRC/runtime/runtime2. Go

// Omit a few fields
type _defer struct {
	sp uintptr // Function stack pointer
	pc uintptr // Program counter
	fn *funcval // Function address
	link *_defer // A pointer to its own structure to link multiple deferred
}
Copy the code

Rules of the convention

  • Rule one: The parameters of the delay function are determined as soon as the defer statement appears
  • Rule 2: Deferred function execution is executed in last-in, first-out order, meaning that defer comes first and executes last
  • Rule 3: A delayed function may operate on the named return value of the main function

Realize the principle of

case

Let’s start with a simple demo. We declared a defer function in the TestDefer() method, and we modified the value s before, during, and after the function. Let’s take a look at what it produces and what its internal execution and implementation logic looks like.

5	func main(a) {
6		s := TestDefer()
7
8		fmt.Println("result => ",s)
9	}
10
11	func TestDefer(a)(result string)  {
12		s := "init"
13
14		defer func(s string) {
15			s = "defer2"
16		}(s)
17
18		defer func(s string) {
19			s = "defer1"
20		}(s)
21
22		s = "return"
23
24		return s
25	}
Copy the code

Go tool objdump -s’ main ‘[program]

TEXT main.TestDefer(SB) /Users/guanjian/workspace/go/program/defer/defer.go
/ / omit...
  defer.go:14           0x10cbd75               e8a6b3f6ff                      CALL runtime.deferprocStack(SB)         
/ / omit...
  defer.go:18           0x10cbdc1               e85ab3f6ff                      CALL runtime.deferprocStack(SB)         
/ / omit...
  defer.go:24           0x10cbe00               e83bbbf6ff                      CALL runtime.deferreturn(SB)            
/ / omit...
  defer.go:18           0x10cbe16               e825bbf6ff                      CALL runtime.deferreturn(SB)            
/ / omit...
  defer.go:14           0x10cbe2c               e80fbbf6ff                      CALL runtime.deferreturn(SB)            
/ / omit...
  defer.go:14           0x10cbe40               c3                              RET                                     
  defer.go:11           0x10cbe41               e8fa11faff                      CALL runtime.morestack_noctxt(SB)       
  defer.go:11           0x10cbe46               e995feffff                      JMP main.TestDefer(SB)                  
/ / omit...
Copy the code

Defer the initial

The Runtime.DeferprocStack is called when you declare defer, storing the function defer into the goroutine’s linked list. This is the initial phase of defer. If the defer declaration contains parameters, they will be initialized and will not be affected by subsequent changes. That means that the input parameters in the initialization phase of defer can only initialize assignments.

Defer to perform

In the return directive, specifically before the RET directive, defer is removed from the Goroutine list and executed, which corresponds to the execution phase of defer.

By decompilating the file, it is executed in the following order:

The order in which the multiple deferred defer will be executed: It can be seen that the order of defer execution is in reverse order of the declaration order, that is, the declaration order is defer-1, defer-> > 2 and defer-3, then the execution order is defer-3, defer-2 and defer-1. They are concatenated by link *_defer.

Case analysis

Test_01, test_02 is a value pass, test_03, test_04 is a reference pass we respectively from the scope, stack execution order, value pass, etc

Case 1

func main(a) {
	s1 := test_01()

	fmt.Println("test_01 => ", s1)	//test_01 => 0
}

func test_01(a) int {
	var i int
	defer func(a) {
		fmt.Println("defer-2 => ",i)// Print 1 here, and the previous defer operation has taken effect
		i++	// Does not affect the return value} ()defer func(a) {
		fmt.Println("defer-1 => ",i)// Print 0 here
		i++	// Does not affect the return value} ()return i // Return the same value
}
Copy the code

Case 2 –

func main(a) {
	s2 := test_02()
	
	fmt.Println("test_02 => ", s2) //test_02 => 2
}

func test_02(a) (res int) {
	var i int
	defer func(a) {
		fmt.Println("defer-2 => ",res)// Print 1 here, and the previous defer operation has taken effect
		res++	// Affect the return value, the final return value} ()defer func(a) {
		fmt.Println("defer-1 => ",res)// Print 0 here
		res++	// Affects the return value} ()return i	// Affects the return value, but is not the only and final influence of the return value
}
Copy the code

Case – 3

func main(a) {
	s3 := test_03()
	fmt.Println("test_03 => ", s3) //test_03 => &{jack}
}

type User struct {
	Name string
}

func test_03(a) *User {
	user := &User{}
	defer func(a) {
		user.Name="jack"	// Does not affect the return value} ()return user // Return the same value
}
Copy the code

Case – 4

func main(a) {
	s4 := test_04()

	fmt.Println("test_04 => ", s4) //test_04 => &{jack}
}

type User struct {
	Name string
}

func test_04(a) (resUser *User) {
	user := &User{}
	defer func(a) {
		resUser.Name="jack" // Affects the return value} ()return user	// Affects the return value, but is not the only and final influence of the return value
}
Copy the code

The logic of case 4 and case 3 is basically the same. Please refer to Case 3

conclusion

  • The delay function parameters defined by defer were determined when the defer statement came out
  • Defer defines the order in reverse of the actual execution order
  • Return is not an atomic operation and is performed by saving the return value (if any) – > defer (if any) – > perform the RET jump
  • It’s good practice to close the resource by using defer as soon as you request it

reference

Go Expert Programming