Go关键字defer的使用和底层实现

 更新时间:2023年11月23日 09:05:17   作者:小许code  
defer是Go语言的关键字,一般用于资源的释放和异常的捕捉,defer语句后将其后面跟随的语句进行延迟处理,就是说在函数执行完毕后再执行调用,也就是return的ret指令之前,本文给大家介绍了Go关键字defer的使用和底层实现,需要的朋友可以参考下

1:defer是什么

defer是Go语言的关键字,一般用于资源的释放和异常的捕捉(比如:文件打开、加锁、数据库连接、异常捕获),defer语句后将其后面跟随的语句进行延迟处理,就是说在函数执行完毕后再执行调用,也就是return的ret指令之前。

1.1 资源释放

资源的释放在代码中有很多场景,比如打开文件描述符资源后,需要进行file.close得到释放,在打开文件后就加上defer,避免在后续因为err导致的return退出忘记释放,文件资源。

func openFile() {
	file, err := os.Open("txt")
	if err != nil {
		return
	}
	defer file.Close() //合理位置
}

常见的加锁场景,业务代码中忘记释放锁,那么会导致资源得不到释放,造成死锁,但是defer就很好解决了这个问题,不管业务逻辑怎么处理,最终还是会释放锁。

func lockScene() {
	 var mutex sync.Mutex
	 mutex.Lock()
	 defer  mutex.Unlock()
	 //业务代码...
}

1.2 捕获异常

Go 语言中 recover 关键字主要用于捕获异常,让程序回到正常状态。recover 可以中止 panic 造成的程序崩溃。它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用

func demo()  {
 defer func() {
  if err := recover(); err !=nil{
   fmt.Println(string(Stack()))
  }
 }()
 panic("unknown")
}

2:defer语法

defer语法相对简单,直接在普通函数之前加一个defer关键字

defer demoFunc(args)

虽然defer语法简单,但是当有多个defer注册时,会以逆序执行(类似栈:先进后出),举个栗子。

func f1() {
	defer fmt.Println("defer1")
	defer fmt.Println("defer2")
	fmt.Println("start")
	fmt.Println("end")
	return
}

这段代码的字符串输出结果是:start、end、defer2、defer1。首先输出的defer字符串在正常的start、end后输出可以很好理解(defer在函数返回前执行),字符串defer2在defer1前输出(逆序执行)。

3:defer与return

Go语言中函数的 return 语句并不是原子级的,实际的执行过程为为设置返回值—>ret指令,defer 语句是在返回前执行,所以返回过程是:「设置返回值—>执行defer—>ret」

4:defer底层实现

要了解defer的实现,先看下defer的底层数据结构和各个参数表示的意义(src/runtime/runtime2.go)

type _defer struct {
   siz     int32    // 参数和返回值的内存大小
   started bool
   heap    bool       //是否分配在堆上面
   openDefer bool     // 是否经过开放编码优化
   sp        uintptr  // sp 计数器值,栈指针
   pc        uintptr  // pc 计数器值,程序计数器
   fn        *funcval // defer 传入的函数地址,也就是延后执行的函数
   _panic    *_panic  // defer 的 panic 结构体
   link      *_defer  // 同一个协程里面的defer 延迟函数,会通过该指针连接在一起
}

defer怎么实现延迟的

通过资料了解到,defer在代码中的位置在编译后会有两部分内容: 1:deferproc负责把要执行的函数保存起来,我们称之为defer注册 2:deferreturn是在defer注册完成(deferproc)后,程序执行后续业务代码,直到通过deferreturn执行注册的defer函数

为啥是逆序执行

defer结构有个link指针,是指向的一个defer单链表的头,每次咱们声明一个defer的时候,就会将该defer的数据插入到这个单链表头部的位置,取defer进行执行的时候,是从单链表的头开始去取的,这就是defer先进后出的原因。 底层代码在src/runtime/panic.go,核心代码做了说明

func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
	gp := getg() //获取goroutine结构
	if gp.m.curg != gp {
		// go code on the system stack can't defer
		throw("defer on system stack")
	}
	...
	d := newdefer(siz) //新建一个defer结构
	if d._panic != nil {
		throw("deferproc: d.panic != nil after newdefer")
	}
	d.link = gp._defer // 新建defer的link指针指向g的defer
	gp._defer = d      // 新建defer放到g的defer位置,完成插入链表表头操作
	d.fn = fn
	d.pc = callerpc
	d.sp = sp
	...
}

如图:先声明defer fun1()、再声明 defer fun2(),fun2()在单链表链表头部。

总结

1:defer关键字后面必须是函数,也叫延迟函数

2:defer是逆序执行(后进先出),延迟函数中的参数在defer声明的时候已经确定了

3:在函数return之前执行延迟函数

到此这篇关于Go关键字defer的使用和底层实现的文章就介绍到这了,更多相关Go defer使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • GO项目实战之Gorm格式化时间字段实现

    GO项目实战之Gorm格式化时间字段实现

    GORM自带的time.Time类型JSON默认输出RFC3339Nano格式的,下面这篇文章主要给大家介绍了关于GO项目实战之Gorm格式化时间字段实现的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-01-01
  • Gin+Gorm实现增删改查的示例代码

    Gin+Gorm实现增删改查的示例代码

    本文介绍了如何使用Gin和Gorm框架实现一个简单的增删改查(CRUD)示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-12-12
  • 深入理解Go gin框架中Context的Request和Writer对象

    深入理解Go gin框架中Context的Request和Writer对象

    这篇文章主要为大家详细介绍了Go语言的gin框架中Context的Request和Writer对象,文中的示例代码讲解详细,对我们深入了解Go语言有一定的帮助,快跟随小编一起学习一下吧
    2023-04-04
  • golang中的defer函数理解

    golang中的defer函数理解

    defer是Go语言中的延迟执行语句,用来添加函数结束时执行的代码,常用于释放某些已分配的资源、关闭数据库连接、断开socket连接、解锁一个加锁的资源,这篇文章主要介绍了golang中的defer函数理解,需要的朋友可以参考下
    2022-10-10
  • go语言生成随机数和随机字符串的实现方法

    go语言生成随机数和随机字符串的实现方法

    随机数在很多时候都可以用到,尤其是登录时,本文就详细的介绍一下go语言生成随机数和随机字符串的实现方法,具有一定的参考价值,感兴趣的可以了解一下
    2021-12-12
  • Go语言单元测试超详细解析

    Go语言单元测试超详细解析

    本文介绍了了Go语言单元测试超详细解析,测试函数分为函数的基本测试、函数的组测试、函数的子测试,进行基准测试时往往是对函数的算法进行测验,有时后一个算法在测试数据的基量不同时测试出的效果会不同我们需要对不同数量级的样本进行测试,下文需要的朋友可以参考下
    2022-02-02
  • Golang 高效排序数据详情

    Golang 高效排序数据详情

    本文我们介绍了怎么使用 Golang 语言标准库 sort 包排序数据,需要注意的是,除了本文使用的类型之外,其它任意类型只要实现 sort.Interface 的三个方法,都可以调用 sort.Sort() 函数排序数据。
    2021-11-11
  • Golang报“import cycle not allowed”错误的2种解决方法

    Golang报“import cycle not allowed”错误的2种解决方法

    这篇文章主要给大家介绍了关于Golang报"import cycle not allowed"错误的2种解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以们下面随着小编来一起看看吧
    2018-08-08
  • Go如何实现Websocket服务以及代理

    Go如何实现Websocket服务以及代理

    这篇文章主要介绍了Go如何实现Websocket服务以及代理方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-04-04
  • Go语言指针使用分析与讲解

    Go语言指针使用分析与讲解

    这篇文章主要介绍了Go语言指针使用分析与讲解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07

最新评论