首页 > 其他分享 >Golang之旅——Defer

Golang之旅——Defer

时间:2023-08-14 19:44:25浏览次数:39  
标签:Defer return 函数 之旅 defer sp Golang 返回值 执行

defer

首先来看一下官方的解析:

  1. A deferred function’s arguments are evaluated when the defer statement is evaluated.
  2. Deferred function calls are executed in Last In First Out order after the surrounding function returns.
  3. Deferred functions may read and assign to the returning function’s named return values.

总结:
规则一:延迟函数的参数在defer语句出现时就已经确定下来了
规则二:延迟函数执行按后进先出顺序执行,即先出现的defer最后执行
规则三:延迟函数可能操作主函数的具名返回值

数据结构

源码包src/src/runtime/runtime2.go:_defer定义了defer的数据结构:

	started bool
	heap    bool
	// openDefer indicates that this _defer is for a frame with open-coded
	// defers. We have only one defer record for the entire frame (which may
	// currently have 0, 1, or more defers active).
	openDefer bool
	sp        uintptr // sp at time of defer
	pc        uintptr // pc at time of defer
	fn        func()  // can be nil for open-coded defers
	_panic    *_panic // panic that is running defer
	link      *_defer // next defer on G; can point to either heap or stack!

	// If openDefer is true, the fields below record values about the stack
	// frame and associated function that has the open-coded defer(s). sp
	// above will be the sp for the frame, and pc will be address of the
	// deferreturn call in the function.
	fd   unsafe.Pointer // funcdata for the function associated with the frame
	varp uintptr        // value of varp for the stack frame
	// framepc is the current pc associated with the stack frame. Together,
	// with sp above (which is the sp associated with the stack frame),
	// framepc/sp can be used as pc/sp pair to continue a stack trace via
	// gentraceback().
	framepc uintptr
  • sp 和 pc 分别代表栈指针和调用方的程序计数器;
  • fn 是 defer 关键字中传入的函数;
  • _panic 是触发延迟调用的结构体,可能为空;
  • openDefer 表示当前 defer 是否经过开放编码的优化;

我们知道defer后面一定要接一个函数的,所以defer的数据结构跟一般函数类似,也有栈地址、程序计数器、函数地址等等。
与函数不同的一点是它含有一个指针,可用于指向另一个defer,每个goroutine数据结构中实际上也有一个defer指针,该指针指向一个defer的单链表,每次声明一个defer时就将defer插入到单链表表头,每次执行defer时就从单链表表头取出一个defer执行。
下图展示多个defer被链接的过程:
image.png
新声明的defer总是添加到链表头部,函数返回前执行defer则是从链表首部依次取出执行。
一个goroutine可能连续调用多个函数,defer添加过程跟上述流程一致,进入函数时添加defer,离开函数时取出defer,所以即便调用多个函数,也总是能保证defer是按FIFO方式执行的。

defer的创建和执行

源码包src/runtime/panic.go定义了两个方法分别用于创建defer和执行defer。

  • deferproc(): 在声明defer处调用,其将defer函数存入goroutine的链表中;
  • deferreturn():在return指令,准确的讲是在ret指令前调用,其将defer从goroutine链表中取出并执行。

可以简单这么理解,在编译在阶段,声明defer处插入了函数deferproc(),在函数return前插入了函数deferreturn()。

defer和return执行顺序

return执行顺序

先来看看return的执行顺序:
return不是原子操作,执行过程是: 保存返回值(若有)-->执行defer(若有)-->执行ret跳转。
即return是将i值存入栈中作为返回值,然后执行跳转。
那return又分为匿名返回值和有名返回值,这两种情况又有一些区别。

有名返回值

package main

import "fmt"

func main() {
	fmt.Println(foo())
}

func foo() (ret int) {
	defer func() {
		ret++
	}()

	return 0
}

// 输出
// 1

首先,return在最后返回了0,注意了,这时候会执行第一步,**ret=0**
在完成赋值后,执行存在的defer,在defer中,我们又执行了ret++,所以ret的值变为了1
所以在最终 foo()函数返回值为1

匿名返回值

package main

import "fmt"

func main() {
	fmt.Println(foo())
}

func foo() int {
	var i int

	defer func() {
		i++
	}()

	return 1
}

// 输出
// 1

上面的return语句,直接把1写入栈中作为返回值,延迟函数无法操作该返回值,所以就无法影响返回值。
所以结论是:第一步先return赋值,第二步再执行defer,第三步执行return返回。

defer与panic

(这部分的内容单独放在错误处理中去描述)

总结

  • defer定义的延迟函数参数在defer语句出时就已经确定下来了
  • defer定义顺序与实际执行顺序相反
  • return不是原子操作,执行过程是: 保存返回值(若有)-->执行defer(若有)-->执行ret跳转
  • 申请资源后立即使用defer关闭资源是好习惯

标签:Defer,return,函数,之旅,defer,sp,Golang,返回值,执行
From: https://www.cnblogs.com/chenchen4396/p/17629547.html

相关文章

  • Golang 内嵌静态资源-转
    转载:https://www.mousemin.com/archives/go-embed-resource/把静态资源嵌入在程序里,原因无外乎以下几点:布署程序更简单。传统部署要么需要把静态资源和编译好的程序一起打包上传,要么使用docker和dockerfile自动化.保证程序完整性。运行中发生静态资源损坏或丢失往往会影响程......
  • Golang: 使用embed内嵌资源文件-转
    转载:https://blog.kakkk.net/archives/71/embed介绍首先,embed是 go1.16才有的新特性,使用方法非常简单,通过 //go:embed指令,在打包时将文件内嵌到程序中。官方文档:https://pkg.go.dev/embed快速开始文件结构.├──go.mod├──main.go└──resources└──hello......
  • Golang: 如何交叉编译
    0.golang可以交叉编译出不同操作系统运行的程序1.在macm2架构下,golang程序mian文件所在的主目录下,即可生成#在命令行进入项目根目录,并执行以下命令CGO_ENABLED=0GOOS=xxxGOARCH=xxxgobuild参数说明:CGO_ENABLED:是否使用 C语言 版本的 GO 编译器。0 表示不......
  • golang简单实现CLHLock,不可重入的clh自旋锁
    如果不想自旋,可以把Lock、waitIsFinish和noticeIsFinish代码中的方式2注释掉,改用方式1。不过实际测试在低并发的情况下,自旋的执行效率更高,要根据实际业务场景选择使用哪种方式。源代码如下:import("runtime""sync/atomic")const(Gosched_Spin_Count=10000......
  • LeetCode 周赛上分之旅 #39 结合中心扩展的单调栈贪心问题
    ⭐️本文已收录到AndroidFamily,技术和职场问题,请关注公众号[彭旭锐]和BaguTreePro知识星球提问。学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度也更复杂。在这个专栏里,小彭与你分享每场LeetCode周赛的解题报告,一......
  • 一个mysql dba的成长之旅--第零章 绝处逢生:意外收到dba offer
    (本故事纯属虚构,如有雷同实属巧合)2018年的一个秋天的下午,江南理工大学图书馆一楼的宣讲会大厅人头攒动,充满了期待的氛围。这里正在举办一场国内知名互联网公司的宣讲会,吸引了众多毕业生前来倾听。小李身穿一套整洁的求职西装,手里拿着整齐的彩色简历,坐在室友旁边,全神贯注地聆听着台......
  • Golang之旅——内存管理
    转载放在最前一文带你了解,虚拟内存、内存分页、分段、段页式内存管理[Golang三关-典藏版]一站式Golang内存洗髓经|Go技术论坛刘丹冰Aceld感谢以上文章作者,收获满满......
  • RTMP流媒体服务器LntonMedia(免费版)视频直播点播平台采用Golang指针问题导致平台重复推
    我们的团队在研发视频流媒体平台时,广泛应用了Go语言。之前我们也与大家交流过关于Go语言指针的问题和应用。如果你对视频流媒体平台编译中如何运用Go语言指针感兴趣,可以了解一下我们的讨论。在对LntonMedia的编译中,我们发现Golang指针问题会导致系统内的重复推流。Golang遍历切片代......
  • golang网络编程
    1简介Go语言的网络编程主要使用net包来实现。该包提供了一组基本的网络功能,包括TCP和UDP套接字、IP地址和端口号的处理、以及一些高级特性,如非阻塞I/O和HTTP客户端库。本文简单介绍一下如何使用net包进行TCP通信2TCP通信TCP服务端处理流程:监听端口接收......
  • golang 构造函数的应用
    在Go语言中,没有类似于传统面向对象编程语言中的构造函数的概念。然而,你可以使用初始化函数来达到类似的效果。在Go中,结构体(struct)是一种用于封装一组相关字段的数据类型。你可以为结构体定义一个初始化函数,该函数在创建结构体实例时自动调用,用于设置字段的初始值。这个初始化函数......