Go —— defer

defer

defer 语句用于延迟函数的调用,常用于关闭文件描述符、释放锁等资源释放场景。但 defer 关键字只能作用于函数或函数调用。

defer func(){						// 函数fmt.Print("Hello,World!")
}()defer fmt.Print("Hello,World!")		// 函数调用

1. 执行机制

1.1 执行时机

在Go语言的函数中 return 语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer 语句执行的实际就在返回值操作后,RET指令前。具体如下图所示:

defer执行时机

1.2 行为规则

1)规则一:延迟函数的参数在 defer 语句出现时就已经确定了

示例如下:

func a() {i := 0defer fmt.Println(i)	// 程序运行打印 0i++return
}

defer 语句中的 fmt.Println() 参数 i 值 在 defer 出现时就已经确定了,实际上是复制了一份。后面对变量 i 的修改不会影响 fmt.Println() 函数的调用,依旧打印 0。

对于指针类型参数,此规则依然适用,只不过延迟函数的参数是一个地址值,在这种情况下,defer 后面的语句对变量的修改可能会影响延迟函数。

2)规则二:延迟函数按照后进先出 的顺序执行

设计 defer 的初衷是简化函数返回时资源清理的动作,资源往往有依赖顺序,比如申请资源的顺序时 A→B→C,释放的顺序往往又要反向进行。这就是把 defer 设计成 LIFO 的原因

3)规则三:延迟函数可能操作主函数的具名返回值

定义 defer 的函数(下称主函数)可能有返回值,返回值可能有名字(具名返回值),也可能没有返回值(匿名返回值),延迟函数可能会影响返回值。

举个栗子:

func deferFuncReturn() (result int){i := 1defer func() {result++}()return i	// 程序返回 2
}

上面已经介绍过了 defer 的执行时机,该函数的 return 语句可以拆分成下面三行:

	result = iresult++return

主函数有不同的返回方式,包括匿名返回值和具名返回值,但万变不离其宗,只要把 return 语句拆开都可以很好理解,下面分别举例说明:

(1)主函数拥有匿名返回值,返回字面值

一个主函数拥有一个匿名返回值,返回时使用字面值,这种情况下 defer 语句时无法操作返回值的

func foo() int {var i intdefer func() {i++)()return 1
}

上面的 return 语句直接把 1 写入栈中作为返回值,延迟函数无法操作该返回值

(2)主函数拥有匿名返回值,返回变量

一个主函数拥有一个匿名返回值,返回本地或全局变量,这种情况下 defer 语句可以引用返回值,反不会改变返回值

func foo() int {var i intdefer func(){i++}return i
}

假定返回值变量为 anony ,上面的返回语句可以拆分为以下过程:

	annnoy = ii++return

函数返回 0

(3)主函数拥有具名返回值

主函数声明语句中带名字的返回值会被初始化为一个局部变量,函数内部可以像使用局部变量一样使用该返回值。如果 defer 语句操作该返回值,则可能改变返回结果。

一个影响函数返回值的例子:

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

上面的函数拆解出来如下所示:

	ret = 0ret++return

函数真正返回前,在 defer 中对返回值做了 +1 操作,所以函数最终返回 1

2. 实现原理

2.1 数据结构

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

type _defer struct {...sp        uintptr // 函数栈指针pc        uintptr // 程序计数器fn        func()  // 函数地址link      *_defer // 指向自身结构的指针,用于链接多个 defer...
}

编译器会把每个延迟函数编译成一个 _defer 实例暂存到 goroutine 数据结构中,待函数结束时再逐个取出执行。
每个defer 语句对应一个 _defer 实例,多个实例使用指针 link 链接起来形成一个单链表,保存到 goroutine 数据结构中。
goroutine 的数据结构如下所示:

type g struct {..._defer *_defer // defer 链表...
}

每次插入 _defer 实例时均插入链表头部,函数执行结束时再依次从头部取出,从而实现后进先出的效果。
一个 goroutine 可能连续调用多个函数,defer 的添加过程跟上述流程一致,进入函数时添加 defer ,离开函数时取出 defer ,所以即便调用多个函数,也总是能保证 defer 是按 LIFO 方式执行的。

3. 小结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/289094.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第十三届国际纯数学与应用数学会议(ICPAM 2024)即将召开!

第十三届国际纯数学与应用数学会议将于2024年7月17日至20日在克罗地亚萨格勒布召开。ICPAM是一项连续成功举办十二年的年度会议,其汇集了纯数学和应用数学领域的教授、研究人员、学者和学生,为跨行业交流,经验分享,学术界合作以及…

ArcGIS Pro横向水平图例

终于知道ArcGIS Pro怎么调横向图例了! 简单的像0一样 旋转,左转右转随便转 然后调整图例项间距就可以了,参数太多就随便试,总有一款适合你! 要调整长度,就调整图例块的大小。完美! 好不容易…

【C++】手撕哈希表的闭散列和开散列

> 作者:დ旧言~ > 座右铭:松树千年终是朽,槿花一日自为荣。 > 目标:手撕哈希表的闭散列和开散列 > 毒鸡汤:谁不是一边受伤,一边学会坚强。 > 专栏选自:C嘎嘎进阶 > 望小伙伴们…

对AOP的理解

目录 一、为何需要AOP?1、从实际需求出发2、现有的技术能解决吗?3、AOP可以解决 二、如何实现AOP?1、基本使用2、更推荐的做法2.1 “基本使用”存在的隐患2.2 最佳实践2.2.1 参考Transactional(通过AOP实现事务管理)2.…

SpringBoot国际化配置流程(超详细)

前言 最新第一次在做SpringBoot的国际化,网上搜了很多相关的资料,都是一些简单的使用例子,达不到在实际项目中使用的要求,因此本次将结合查询的资料以及自己的实践整理出SpringBoot配置国际化的流程。 SpringBoot国际化 "i…

用系统观念打造智慧公厕,引领智慧城市的发展

智慧公厕,作为智慧城市建设的一部分,具有重要意义。在高度发达的科技条件下,如何打造高质量的智慧公厕是一个值得思考的问题。本文将以智慧公厕源头实力厂家广州中期科技有限公司,大量精品案例项目现场实景实图实例,探…

Rust控制台输出跑马灯效果,实现刷新不换行,实现loading效果

要在 Rust 中实现控制台刷新而不换行,以实现类似 "loading" 状态的效果,你可以使用 \r(回车符)来覆盖上一行的内容。 use std::io::{self, Write}; use std::thread; use std::time::Duration;fn main() {let loading_…

浅模仿小米商城布局(有微调)

CSS文件 *{margin: 0;padding: 0;box-sizing: border-box; }div[class^"h"]{height: 40px; } div[class^"s"]{height: 100px; } .h1{width: 1528px;background-color: green; } .h11{background-color:rgb(8, 220, 8); } .h111{width: 683px;background-c…

动态内存操作函数使用过程中会遇见的问题

越界访问 首先我们上一个代码,看看这个的代码的问题 这个代码的问题显而易见 ,就是在循环里面,产生了越界访问的问题,这里你开辟了10个整形空间,但是从0-10一共是11个整形空间。导致访问不合法的空间,从而…

卷积神经网络-卷积层

卷积神经网络-卷积层 1多层感知机(MLP)2卷积神经网络(CNN)3MLP和CNN关系与区别4仍然有人使用MLP的原因:5MLP的局限性:MLP的应用领域:总结:6全连接到卷积全连接层 vs 卷积层结构差异应…

一文教你学会用群晖NAS配置WebDAV服务结合内网穿透实现公网同步Zotero文献库

文章目录 前言1. Docker 部署 Trfɪk2. 本地访问traefik测试3. Linux 安装cpolar4. 配置Traefik公网访问地址5. 公网远程访问Traefik6. 固定Traefik公网地址 前言 Trfɪk 是一个云原生的新型的 HTTP 反向代理、负载均衡软件,能轻易的部署微服务。它支持多种后端 (D…

LLLM并发加速部署方案(llama.cpp、vllm、lightLLM、fastLLM)

大模型并发加速部署 解析当前应用较广的几种并发加速部署方案! llama.cpp vllm lightLLM fastLLM

使用yolov9来实现人体姿态识别估计(定位图像或视频中人体的关键部位)教程+代码

yolov9人体姿态识别: 相较于之前的YOLO版本,YOLOv9可能会进一步提升处理速度和精度,特别是在姿态估计场景中,通过改进网络结构、利用更高效的特征提取器以及优化损失函数等手段来提升对复杂人体姿态变化的捕捉能力。由于YOLOv9的…

Java SPI 机制

SPI 机制的定义 在Java中,SPI(Service Provider Interface)机制是一种用于实现软件组件之间松耦合的方式。它允许在应用程序中定义服务接口,并通过在类路径中发现和加载提供该服务的实现来扩展应用程序功能。 SPI 机制通常涉及三…

ubuntu 中安装docker

1 资源地址 进入ubuntu官网下载Ubuntu23.04的版本的镜像 2 安装ubuntu 这里选择再Vmware上安装Ubuntu23.04.6 创建一个虚拟机,下一步下一步 注意虚拟机配置网络桥接,CD/DVD选择本地的镜像地址 开启此虚拟机,下一步下一步等待镜像安装。 3…

Idea2023.3.6版本无法启动设置界面-settings界面打不开无反应---IntelliJ Idea工作笔记013

先说一下网上有,把某个文件删除的 有说是因为汉化问题的 可以看到,其实都不是,这样弄就好了,很简单 Please report thisjava.lang.ClassCastException: class [Lcom.intellij.execution.filters.CompositeInputFilter$InputFilterWrapper; cannot be cast to class java.uti…

Java多线程实战-从零手搓一个简易线程池(二)线程池与拒绝策略实现

🏷️个人主页:牵着猫散步的鼠鼠 🏷️系列专栏:Java全栈-专栏 🏷️本系列源码仓库:多线程并发编程学习的多个代码片段(github) 🏷️个人学习笔记,若有缺误,欢迎评论区指正…

文件操作(下)(想要了解如何操作文件,那么看这一片就足够了!)

前言:在文件操作(上)中,我们讲到了基础的文件操作,包括文件的打开,文件的关闭,以及文件的基础读写,那么除了之前学习的读写之外,还有什么其他的方式对文件进行读写操作吗…

Python提示‘ModuleNotFoundError: No module named ‘numpy.core._multiarray_umath‘

一、问题背景 在学习Python编程使用matplotlib时,总是提示: ModuleNotFoundError: No module named numpy.core._multiarray_umath 问题大致描述如下: D:\WorkSpace\PythonWorkSpace\Python编程-从入门到实践\venv\Scripts\python.exe D:\WorkSpace\Pyt…

Jenkins用户角色权限管理

Jenkins作为一款强大的自动化构建与持续集成工具,用户角色权限管理是其功能体系中不可或缺的一环。有效的权限管理能确保项目的安全稳定,避免敏感信息泄露。 1、安装插件:Role-based Authorization Strategy 系统管理 > 插件管理 > 可…