Golang的defer底层原理是怎么样的?

Go defer底层原理与高危陷阱实战解析 defer是Go语言中用于资源清理和异常恢复的核心机制,其本质并非简单‘延迟执行’,而是编译器在函数入口注册、运行时按LIFO链表管理的清理动作。它涉及参数求值时机、命名返回值修改、panic恢复链控制等关键原理,直接影响内存安全、文件描述符泄漏和HTTP连接复用效率。在微服务、API网关及高并发场景中,defer误用常导致CPU飙升、QPS骤降、TIME_WAIT堆积等生产事故。本文结合Go 1.22 runtime源码(如deferproc1、_defer结构体)、pprof性能分析与真实压测数据,系统 阅读详情

前言

如果你用过 defer ,那么你会大概知道 defer 是用于延迟函数的调用。每次调用 defer 都会将一个函数压入栈中,执行的时候从栈顶取出函数来执行,那么它底层的原理到底是怎么样的?今天我们一起来探究一下。

defer 的使用规则

延迟函数的参数在defer语句出现时就已经确定下来了
func test() {
    num := 0
    defer fmt.Println(num)
    num = 3
    return
}
输出:0

在定义 defer fmt.Println(num) 的时候,参数就已经复制了一份,传递过去了,所以输出的结果为 0

延迟函数执行按后进先出顺序执行,即先出现的 defer最后执行

即是后进先出,类似于进栈出栈,如果你接着往下看,就会了解到,它底层就是维护了一个单链表。新建 defer 的时候,就压入单链表的头部,调用的时候,就从头部取出一个 defer,这样就有了先进后出的效果
在这里插入图片描述

延迟函数可能操作主函数的具名返回值(面试常考)

怎么理解这句话呢?

定义一个函数,如果这个函数的返回值是匿名的,那么 defer 不会影响到这个匿名返回值

定义一个函数,如果这个函数的返回值是具名的,就是我给这个返回值定义了变量名,那么 defer 可能会影响到这个 具名返回值。

有朋友说,你光说这么两句话,我也不太理解这是什么意思呀?别急,本文的最后一个小节,我会举例说明一下。

defer 底层数据结构

go/src/runtime/runtime2.go

defer 的结构体如下,

type _defer struct {
	siz     int32 // includes both arguments and results
	started bool
	heap    bool
	openDefer bool
	sp        uintptr  // stack point 栈指针
	pc        uintptr  // process count 程序计数器
	fn        *funcval // can be nil for open-coded defers
	_panic    *_panic  // panic that is running defer
	link      *_defer  // 指向下一个 defer,因为这是一个单链表
	fd   unsafe.Pointer // funcdata for the function associated with the frame
	varp uintptr       
	framepc uintptr
}

goroutine 的结构体如下,比较主要的字段,我都加了注释。可以看出,这里是有一个专门的字段用来保存 defer 的,它的底层是一个单链表。每次定义一个 defer 都会将 defer 插入到单链表头部,每次执行时,都从单链表头部获取 一个 defer,这样就有后进先出的执行效果。

type g struct {
	stack       stack   // 协程的 栈,
	sched     gobuf  // 在协程切换的时候,用于保存上下文
	goid         int64  //goroutine 的ID
	gopc           uintptr         // pc of go statement that created this goroutine
	startpc        uintptr         // pc of goroutine function
	_defer    *_defer // defer 指针,指向一个 defer 单链表,每次定义一个 defer 都会插入到单链表头部,每次执行时,都从单链表头部获取 一个 defer,这样就有后进先出的执行效果。
	此处省略很多参数

defer 指针,指向一个 defer 单链表,每次定义一个 defer 都会插入到单链表头部,每次执行都从从不获取,这样就有后进先出的执行效果。

那么,基于上面的解释,我们来画图理解一下:
在这里插入图片描述

defer 的创建和执行

源码在

/usr/local/go/src/runtime/panic.go

我们可以找到 deferproc() 和 deferreturn() 这两个函数,这两个方法分别用于创建和执行 defer。

deferproc() 做了什么事情呢?
  • 获取 deferproc之前的 sp 寄存器的值
  • 创建一个 _defer 结构体,然后把这个 _defer 放到 goroutine 的 *_defer 字段,并且将他放到当前的 _defer 链表的头
  • 初始化 _defer 结构体的参数
  • return0
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")
	}

	if goexperiment.RegabiDefer && siz != 0 {
		// TODO: Make deferproc just take a func().
		throw("defer with non-empty frame")
	}
	sp := getcallersp() //获取当前寄存器的值
	argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
	callerpc := getcallerpc()

	d := newdefer(siz)  //创建 defer 结构体实例
	if d._panic != nil {
		throw("deferproc: d.panic != nil after newdefer")
	}
	d.link = gp._defer
	gp._defer = d //给 goroutine 的单链表赋值
	d.fn = fn
	d.pc = callerpc
	d.sp = sp
	switch siz {
	case 0:
		// Do nothing.
	case sys.PtrSize:
		*(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))
	default:
		memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))
	}
	return0()
}

deferreturn():在return指令,准确的讲是在ret指令前调用,其将defer从goroutine链表中取出并执行。
可以简单这么理解,在编译在阶段,声明defer处插入了函数deferproc(),在函数return前插入了函数deferreturn()。

  • 获取 g、获取 p
  • 执行 defer
  • 运行结束后释放 defer
func deferreturn() {
	gp := getg()
	d := gp._defer
	if d == nil {
		return
	}
	sp := getcallersp()
	if d.sp != sp {
		return
	}
	if d.openDefer {
		done := runOpenDeferFrame(gp, d)
		if !done {
			throw("unfinished open-coded defers in deferreturn")
		}
		gp._defer = d.link
		freedefer(d)
		return
	}
	argp := getcallersp() + sys.MinFrameSize
	switch d.siz {
	case 0:
		// Do nothing.
	case sys.PtrSize:
		*(*uintptr)(unsafe.Pointer(argp)) = *(*uintptr)(deferArgs(d))
	default:
		memmove(unsafe.Pointer(argp), deferArgs(d), uintptr(d.siz))
	}
	fn := d.fn
	d.fn = nil
	gp._defer = d.link
	freedefer(d)
	_ = fn.fn
	jmpdefer(fn, argp)
}

defer 对于不同情况的处理

匿名返回值
func test() (int) {
	t := 2
    defer func() {
        t = 10
    }()

    return t
}

func main() {
    fmt.Println(test())
}
输出 2
具名返回值
func test() (t int) {  //t初始化0, 并且作用域为该函数全域

    defer func() {
        t = t * 10
    }()

    return 1
}

func main() {
    fmt.Println(test())
}
输出 10
发生 panic
func main() {
    defer_call()

    fmt.Println("main 正常结束")
}

func defer_call() {
    defer func() { fmt.Println("defer: panic 之前1") }()
    defer func() { fmt.Println("defer: panic 之前2") }()

    panic("异常内容")  //触发defer出栈

	defer func() { fmt.Println("defer: panic 之后,永远执行不到") }()
}
输出:
defer: panic 之前2
defer: panic 之前1
panic: 异常内容
//... 异常堆栈信息
Splunk CVE-2018-11409认证绕过漏洞深度解析 Splunk作为主流企业级日志分析平台,其Web管理接口的认证机制直接关系到整个安全运营体系的可信边界。CVE-2018-11409本质上是REST API路由层会话校验缺失引发的未授权访问问题,根源在于`/en-US/splunkd/__raw/`路径绕过了splunkweb与splunkd之间的信任链验证,导致配置文件、用户哈希、App源码等敏感信息明文泄露。该漏洞凸显了Web接口权限模型设计失守的风险,尤其在默认配置未加固、HTTPS未启用、初始密码未修改的生产环境中危害急剧放大。结合vulfocus 阅读详情

相关推荐

黑马程序员Pink的javaScript课程APIday4

本文摘要:介绍了JavaScript中日期对象的实例化方法(有参/无参)及三种获取时间戳的方式。讲解了DOM节点操作,包括节点查找(父、子、兄弟节点)、增加/克隆/删除节点的方法。详细说明了移动端touch事件和Swiper插件的使用,包括自动播放和小圆点配置。最后提及学成在线案例中表单验证和取消默认跳转的实现。

JLY19970726的博客 826

GoLang之defer底层系列一(v 1.12)

GoLang之defer底层系列一(v 1.12)

weixin_52690231的博客 627

2025海外代理IP深度横评:Bright Data、IPFoxy、SmartProxy等六大服务商实战对比

本文深度横评2025年六大海外代理IP服务商(Bright Data、IPFoxy、SmartProxy、IPIPGO、Kookeey及青果网络),基于实战测试数据,从IP池质量、速度稳定性、性价比等维度进行对比。针对跨境电商、数据爬虫、社交媒体管理等不同业务场景,提供精准的选购指南与避坑建议,帮助企业用户根据自身需求选择最合适的代理IP解决方案。

weixin_29196891的博客 435

一. go 常见控制结构实现原理之 defer

查看defer底层结构体,实际是一个FIFO链表, 多个defer时会用内部的link连接,使用defer有三条规则规则一:延迟函数的参数在defer语句出现时就已经确定下来了规则二:延迟函数执行按后进先出顺序执行,即先出现的defer最后执行规则三:延迟函数可能操作主函数的具名返回值。

qq_29799655的博客 329

SAP Joule Studio 全景解析

摘要:SAP Joule Studio是SAP Build平台中的低代码/专业级复合工作台,用于构建和管理企业专属的Joule智能体与技能。它支持通过拖拽和声明式编排实现跨系统任务自动化,与SAP及第三方系统集成。核心构件包括Agent(智能体)、Skill(技能)、Action(动作)和Project(项目)。Joule Studio强调企业级落地,与SAP Build Process Automation等工具协同,实现从设计到运行的全生命周期管理。典型应用场景包括供应链补货、财务关账和人力服务台自动化

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 396

【机器人技术基础】MATLAB Robotics Toolbox PUMA560 运动学正逆解

机器人技术基础实验2报告 1. 实验目的和要求 2. 实验手段 2.1 实验原理 2.2 函数详解 3. 实验主要内容 4. 实验结果和分析 4.1 正运动学求解 例1 例2 4.2 逆运动学求解 例1 例2 1. 实验目的和要求 掌握常见形式的机器人的D-H坐标系建立方法、运动学建模; 正逆运动学分析与求解; 使用Robotics Toolbox对直角坐标机器人、圆柱坐标机器人、球坐标机器人、PUMA560其中之一建立运动学模型,正逆求解和仿真。 2. 实验手段 2.1

【人工智障 | 计算鸡视觉 | 机器孩】 1万+

Golang中defer的实现原理

前言 在Go语言中,可以使用关键字defer向函数注册退出调用,即主函数退出时,defer后的函数才被调用。defer语句的作用是不管程序是否出现异常,均在函数退出时自动执行相关代码。 所以,defer后面的函数通常又叫做延迟函数 defer规则 1.延迟函数的参数在defer语句出现时就已经确定下来了 func a() { i := 0 defer fmt.Println(i) i++ return } 返回结果: defer语句中打印的变量i在defer出现时就已经拷

香烟,瓜子,矿泉水 1187

Python爬虫实战:高效爬取中国版权登记数据

本文将详细介绍如何使用Python最新技术栈构建一个高效、稳定的中国版权登记数据爬虫系统。我们将使用Playwright+Asyncio实现无头浏览器自动化,结合Redis分布式任务队列和MongoDB数据存储,打造一个能够应对反爬机制的企业级数据采集方案。文章包含完整代码实现、性能优化技巧以及法律合规建议,帮助开发者快速掌握大规模数据采集的核心技术。本文详细介绍了2024年使用Python最新技术栈构建中国版权登记数据爬虫的完整方案。基于Playwright的无头浏览器自动化。

2201_76125261的博客 1899

golang的defer机制详解

defer概述 defer用来声明一个延迟函数,把这个函数放入到一个栈上,当外部的包含方法return之前,返回参数到调用方法之前调用,也可以说是运行到最外层方法体时调用。我们经常用他来做一些资源的释放,比如关闭io操作。 defer是golang的一个特色功能,被称为“延迟调用函数”。当外部函数返回后执行defer。类似于其他语言的 try… catch … finally…...

zhongcanw的博客 5033

【银河麒麟V10】【桌面】【服务器】系统启动问题

1、如果之前运行好好的,重启后系统报错,一般80%是因为fstab的问题,回想下之前有没有添加过外接存储,修改过/etc/fstab,如果是的话一般是因为存储异常(比如网络存储无法连接,本地存储路径变化)导致系统开机自检失败,导致无法正常启动; 处理方式:进入单用户(单用户进入的几种方式上面有专门文章介绍),修改/etc/fstab,将新增的内容暂时注释掉,然后重启系统,能正常进入系统后再分析失败原因。 ...

桂安俊@KylinOS 3万+

MIPI D-PHY TX 一致性测试实例解析 Part 01

汽车电子领域,尤其是ADAS,伴随着摄像头的大规模布局,MIPI信号的使用日益频繁,关于它的验证与分析工作也变得日益关键

一只豌豆象的博客 1万+

【go】defer底层原理

golang defer原理

weixin_40660221的博客 738

【定位模块项目实战】正态分布变换(NDT)定位及建图(依赖高精度点云地图和三维激光雷达的定位方案)

系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 TODO:写完再整理 文章目录系列文章目录前言一、正态分布变换(NDT)定位解决的主要问题二、正态分布变换(NDT)定位的核心思想三、NDT算法流程1.多元正态分布概念2.网格化并计算正态分布参数3.变换参数和最大似然四、使用C++实现NDT配准参考资料 前言 认知有限,望大家多多包涵,有什么问题也希望能够与大家多交流,共同成长! 本文先对依赖高精度地图和激光雷达的定位技术–正态分布变换(NDT)定位做个简单的介绍,具体

qq_35635374的博客 4412

Go defer(一):延迟调用的使用及其底层实现原理详解

defer是Go语言提供的一种用于注册延迟调用的机制,让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行。在资源释放、耗时统计等场景有着很高的使用频率。本文以Go 1.16版本的代码为例,主要介绍defer使用特点,其底层实现原理。

dahao_up39的博客 1457

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Golang底层原理剖析之defer

defer层原理剖析Go1.12Go1.13Go1.14 Go1.12 关于defer,我们知道它会在函数返回之前倒叙执行,像这样的代码,编译后的伪指令是这样的 defer指令对应到两部分内容,deferproc负责把要执行的函数保存起来,我们称之为defer注册,deferproc函数会返回0,和panic recover有关,先忽略,对应要跳转的也先忽略。defer注册完成后程序会继续执行后面的逻辑,直到返回之前通过deferreturn执行注册的defer函数。正是因为先注册后调用,才实现了def

清风 1195

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

go底层系列-defer原理剖析

go底层系列-defer原理剖析 目录go底层系列-defer原理剖析defer前言热身defer规则规则一:延迟函数的**参数在defer语句出现时就已经确定下来**了规则二:延迟函数执行按后进先出顺序执行,即**先出现的 defer最后执行**规则三:延迟函数可能操作主函数的具名返回值函数返回过程主函数拥有匿名返回值,返回字面值主函数拥有匿名返回值,返回变量主函数拥有具名返回值defer 实现原理defer 数据结构defer的创建和执行总结 defer 前言 defer语句 用于延迟函数的调用 每

wys74230859的博客 808

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

golang中defer的底层原理

go的defer其实是有两个方案的:1.协程记录defer信息,函数退出时调用2.将defer代码直接编译进函数尾

xiaodongdonga的博客 165

golang工程——底层原理【defer、panic、recover、interface】

defer panic recover interface底层原理

qq_43058348的博客 447

defer和return由浅入深,讲解底层。

做一个简单的区别这个return和defer的底层原理,这个是我从自己的语雀复制过来可能,没有做特别的格式对齐,我把网址附上,想要了解的都可以具体访问这个网址https://www.yuque.com/chenxiangyang-n12yg/pli7v5/mgq74fwc64eestot?singleDoc# 《2.defer和return》如果有什么错误的地方也希望大家提出。本文主要是针对的底层原理和基本应用,像return返回函数类型那些,就不再过多介绍。

2303_79710813的博客 1944

Go defer底层原理与生产级避坑指南

defer是Go语言中实现确定性资源清理的核心机制,其本质并非语法糖,而是绑定函数栈帧退出事件的运行时调度钩子。它基于单向链表结构管理执行顺序,与panic传播、goroutine生命周期及GC模型深度协同。理解defer的参数求值时机、闭包捕获行为、有名返回值交互等原理,可避免连接池泄漏、悬垂指针、panic拦截失效等高频线上故障。在微服务、数据库驱动和网络中间件开发中,defer直接决定百万QPS下的稳定性边界。本文结合源码级验证与压测数据,解析defer在真实生产环境中的技术价值与落地约束。

weixin_34006468的博客 481

Go Quiz: 从Go面试题看defer语义的底层原理和注意事项

面试题 这是Go Quiz系列的第4篇,关于Go语言的defer语义。 这道题稍微有点迷惑性,通过这道题可以加深我们对Go语言defer关键字底层运行机制的理解。

perfume 306

深入 Go 底层原理(十):defer 的实现与性能开销

语句是 Go 语言提供的一种用于延迟函数调用的机制,通常用于资源释放、解锁、记录日志等场景。它能保证在函数返回前,无论函数是正常返回还是发生。使用起来非常方便,但它的实现并非零成本。函数返回时,会逆序遍历这个链表并执行调用。循环之外,且数量固定,编译器会采用基于栈的实现。指令,将函数和参数信息存入栈上的预留空间。:编译器会在函数的栈帧上预留空间来记录。就被计算和固定的,而不是在函数返回时。的信息(要调用的函数指针和参数)。,才会退回到旧的、基于堆分配的实现。在函数返回前,会插入一个。指令,它会检查栈上的。

dl999666的博客 315

【go】go中defer实现原理

deferdefer。

m0_74282926的博客 1293

Golang 之轻松化解 defer 的温柔陷阱

作者 |饶全成责编 | 胡巍巍defer是Go语言提供的一种用于注册延迟调用的机制:让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致...

优快云资讯 2204

Golang之defer延迟函数调用

文章目录1.什么是defer?2.defer进阶2.1. defer的底层原理是什么2.2.defer命令拆解3.更多知识点3.1.很多有返回值的内置函数是不能被延迟调用的3.2.延迟调用的函数值的估值时刻 1.什么是defer? defer是Go语言提供的一种用于注册延迟调用的机制;让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行。 de...

MiixTimer的博客 390

2020-11-19:go中,defer原理是什么?

福哥答案2020-11-19: 什么是defer defer是go语言提供的一种用于注册延迟调用的机制:让函数或者语句在当前函数执行完毕(包括return正常结束或者panic导致的异常结束)之后执行。 defer语句通常用于一些成对的操作场景,打开/关闭连接,加锁/解锁,打开文件/关闭文件等等 defer在一些需要回收资源的场景中非常有用 为什么需要defer 有效防止内存泄漏 defer底层原理 每次defer语句在执行的时候,都会将函数进行“压栈”,函数参数会被拷贝下来。当外层函数

福大大架构师每日一题 2万+
上一篇: Golang中的map是线程安全的吗?
下一篇: 从源码剖析Golang的切片是如何扩容的?
zone7_
博客等级 码龄10年 144粉丝 133原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值