Golang语言学习从入门到实战----defer

defer

在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制)

简单的demo

package main

import "fmt"

func main() {
	res := sum(10, 20)
	fmt.Println("number4 res=", res)
}

func sum(n1 int, n2 int) int {
	// 函数执行完毕后,及时的释放资源
	// 先进后出
	defer fmt.Println("number1 n1 = ", n1)
	defer fmt.Println("number2 n2 = ", n2)

	res := n1 + n2
	fmt.Println("number3 res = ", res) // 最先执行
	return res
}

输出:

number3 res =  30
number2 n2 =  20
number1 n1 =  10
number4 res= 30
  • go执行到一个defer时,不会立即执行defer后的语句,而是将defer后的语句压入到一个"栈"(比喻)中, 然后继续执行函数下一个语句。

  • 当函数执行完毕后,在从defer栈中,依次从栈顶取出语句执行(注:遵守栈先入后出的机制)

  • 在defer将语句放入到栈时,也会将相关的值拷贝同时入栈

package main

import "fmt"

/*
输出:
number3 res =  30
number2 n2 =  20
number1 n1 =  10
number4 res= 30
*/
func main() {
	res := sum(10, 20)
	fmt.Println("number4 res=", res)
}

func sum(n1 int, n2 int) int {
	// 函数执行完毕后,及时的释放资源
	// 先进后出
	defer fmt.Println("number1 n1 = ", n1)
	defer fmt.Println("number2 n2 = ", n2)
	
	// 增加一段
	n1++
	n2++
	
	res := n1 + n2
	fmt.Println("number3 res = ", res) // 最先执行
	return res
}

输出:

number3 res =  32 // 输出结果为32
number2 n2 =  20 // 栈中的数值仍是存入前的数值
number1 n1 =  10
number4 res= 32

使用defer+recover来处理错误

package main

import (
	"fmt"
	"time"
)

func main() {

	test()
	for {
		fmt.Println("main()下面的代码...")
		time.Sleep(time.Second)
	}
}

func test() {
	defer func() {
		err := recover() // recover()内置函数,可以捕获到异常
		if err != nil {  // 捕获到异常
			fmt.Println("err=", err)
		}
	}()

	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println("res=", res)
}

输出:

err= runtime error: integer divide by zero
main()下面的代码...

defer在Go中的数据结构

type _defer struct {
	siz     int32
	started bool
	sp      uintptr
	pc      uintptr
	fn      *funcval
	_panic  *_panic
	link    *_defer
}

runtime._defer 结构体是延迟调用链表上的一个元素,所有的结构体都会通过 link字段串联成链表。

在这里插入图片描述

  • siz 是参数和结果的内存大小;
  • sp 和 pc 分别代表栈指针和调用方的程序计数器;
  • fn 是 defer 关键字中传入的函数;
  • _panic 是触发延迟调用的结构体,可能为空;

除了上述的这些字段之外,runtime._defer 中还包含一些垃圾回收机制使用的字段,这里为了减少理解的成本就都省去了

小结

defer关键字的实现主要依靠编译器和运行时的协作

编译期;

defer 关键字被转换runtime.deferproc
在调用defer 关键字的函数返回之前插入runtime.deferreturn;

运行时:

runtime.deferproc会将一个新的 runtime._defer结构体追加到当前Goroutine的链表头;
runtime.deferreturn会从 Goroutine的链表中取出runtime._defer 结构并依次执行;

  • 后调用的defer函数会先执行:
    • 后调用的defer函数会被追加到Goroutine _defer链表的最前面;
    • 运行runtime._defer时是从前到后依次执行;
  • 函数的参数会被预先计算;
    • 调用runtime.deferproc函数创建新的延迟调用时就会立刻拷贝函数的参数,函数的参数不会等到真正执行时计算;

更多关于Golang defer的编译过程与运行过程可以参考:

参考链接:https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-defer/#533-

发布了140 篇原创文章 · 获赞 50 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_43442524/article/details/104693362