defer学习指南

简介: 本文会带你深入浅出从源头、定义、应用、底层依次深入学习defer。结尾附有go语言版本的迭代史哦~

 一、源头:

早期管理资源(如数据库连接、锁、文件句柄、网络连接)和状态清理异常麻烦。

必须在每个可能的返回点(return、err、panic)手动重复清理代码,极易遗漏且打断主要逻辑思路!

像Java语言虽然用了Try-Catch,但缺点是逻辑不清晰、臃肿、不容易判断错误出在什么地方。

作为新生代的Go,及众多语言之精华,推出了defer处理机制。

尤其是在Go1.14版本时,性能开销接近零,更无后顾之忧。

二、定义:

1、语法:

defer functionCall(..arguments..)

image.gif

defer后面直接跟一个函数调用(可以是命名函数/匿名函数/方法...)

2、定义:

当函数执行到defer语句时(注册时),他会立即求值此时该函数调用的参数并将此次函数调用(包括已求值的参数)放到一个延迟调用表中。这个调用函数与goroutine关联,采用LIFO(后进先出的方式调用)。切记这个延迟调用表不会立即执行,而是会等到(函数真正结束之--函数(return或panic)之)在调用。

3、特性:

1、延迟执行:运行到defer时,只是将求值后的参数与调用的函数一并打包到延迟调用表中,需等到函数体结束之后在执行

2、LIFO方式执行:后进先出的方式执行

3、参数求值时机:defer 语句中的函数参数的值,是在执行到defer语句时(即注册时)就确定并保存下来的,而不是在延迟函数实际执行时才求值。

4、作用域:自各函数体

三、应用:

1、临近释放:(逻辑清晰)

mu.Lock()
defer mu.Unlock() // 好习惯!确保解锁
// ... 操作共享数据 ...

image.gif

2、panic补获:(防止程序崩溃)

特殊情况,根据源码分析---协程中出现panic,若不能再该协程中捕获,则会导致整个程序崩溃。

func test(){
    defer func(){
        if r := recover(); r!=nil{
            fmt.Println(r);
        }
    }()
    panic(1);
}

image.gif

3、循环函数释放:(利用完资源后,及时释放资源)

// 正确做法:将文件处理封装到函数,defer 在每次循环的匿名函数结束时执行
func outerFunc() {
    for _, filename := range filenames {
        func() { // 匿名函数
            f, err := os.Open(filename)
            if err != nil {
                log.Println(err)
                return // 退出匿名函数
            }
            defer f.Close() // 延迟到当前匿名函数结束时执行 (即本次循环结束)
            // ... 处理 f ...
        }() // 立即调用匿名函数
    }
}

image.gif

4、查看执行顺序:

代码右上角,有个运行小按钮,点击运行查看。

package main
import (
  "fmt"
  "log"
)
func g(i int) {
  if i > 1 {
    fmt.Println("Panicking!")
    panic(1)
  }
  defer fmt.Println("Defer in g", i)
  fmt.Println("Printing in g", i)
  g(i + 1)
}
func f() {
  defer func() {
    if r := recover(); r != nil {
      log.Println("Recovered in f", r)
    }
  }()
  fmt.Println("Calling g.")
  g(0)
  fmt.Println("Returned normally from f.")
}
func main() {
  f()
}

image.gif

四、底层:

这个是我扒出来的底层源码,重点了解heap、link这俩。

type _defer struct {
  heap      bool    //表示是分配在堆上还是栈上。
  rangefunc bool      // true for rangefunc list
  sp        uintptr   // 栈指针
  pc        uintptr   // 程序计数器
  fn        func()    // 表示需要被延迟执行的函数。
  link      *_defer   // 指向下一个 _defer 结构体的指针。
  // If rangefunc is true, *head is the head of the atomic linked list
  // during a range-over-func execution.
  head *atomic.Pointer[_defer]
}

image.gif

  • defer 语句注册时,会创建一个 _defer 结构体实例。
  • 多个 defer 通过 link 字段形成一个单链表(LIFO 栈),挂载到当前 goroutine 的结构上(g._defer)。新的 defer 总是插入链表头部。

主要有两大种分配方式。

1、堆栈分配

区别:分配位置的不同

获取到runtime_defer结构体,它都会被追加到所在 Goroutine _defer 链表的最前面。

image.gif 编辑

2、开放编码

不建额外结构,直接把 defer 代码塞到函数退出前,用位掩码控制执行,开销几乎和普通调用一样。

3、选择:

首先考虑开放编码(已经优化到:实际消耗跟调用普通函数差不多的地步),后栈分配保底堆分配

以下是整理的Go版本迭代全史,有兴趣的可以一看,挺有趣的。


📅 Go 版本迭代全史(2009–2025)

早期阶段

版本 发布时间 核心特性
初始开源 2009-11-10 正式开源,获得 TIOBE 年度语言称号
Go r56 2011-03-16 首个稳定版本
Go 1.0 2012-03-28 首个正式版本,承诺向后兼容性;引入 go tool pprofgo vet

🔄 每半年发布周期(2013 年起)

版本 发布时间 核心特性
Go 1.1 2013-05-13 重写调度器(支持 Work-Stealing 算法);引入竞态检测器
Go 1.2 2013-12-01 支持全切片表达式;go test 支持覆盖率统计
Go 1.3 2014-06-18 栈模型改为连续栈;引入 sync.Pool
Go 1.4 2014-12-10 支持 Android;运行时从 C 改为 Go;移除 src/pkg 层级
Go 1.5 2015-08-19 自举(移除 C 代码);优化 GC(延迟降至 30ms);引入 vendor 机制
Go 1.6 2016-02-17 默认支持 HTTP/2;GC 延迟进一步降低
Go 1.7 2016-08-15 引入 context 包;SSA 后端优化(性能提升 5–35%)
Go 1.8 2017-02-17 GC 延迟降至亚毫秒级;defer 性能提升 50%
Go 1.9 2017-08-24 引入类型别名;新增并发安全的 sync.Map
Go 1.10 2018-02-16 构建缓存(Build Cache)默认开启
Go 1.11 2018-08-25 引入 Go Modules;支持 WebAssembly
Go 1.12 2019-03-01 优化 TLS 1.3 支持;改进模块机制
Go 1.13 2019-09-03 支持二进制/八进制字面量;错误处理增强(errors.Is/As/Unwrap
Go 1.14 2020-02-25 接口允许方法集重叠;Goroutine 支持异步抢占调度;defer 性能接近零开销
Go 1.15 2020-08-11 优化链接器;改进内联策略
Go 1.16 2021-02 支持静态文件嵌入;默认启用 Go Modules

🚀 重大革新阶段(2022–2025)

版本 发布时间 核心特性
Go 1.18 2022-03-15 引入泛型;支持模糊测试(Fuzzing);工作区模式(Multi-Module Workspaces)
Go 1.23 2025 年初 引入迭代器(seq/seq2);gopls 现代化工具链;go get 管理工具链
Go 1.24 2025 年中 标准库支持 strings/slices/maps 迭代器;增强 WebAssembly 安全性与性能
Go 1.25 2025-08 (预计) 移除核心类型(Core Types);简化泛型规范;优化错误提示

太多了不好记,有兴趣查看时,可以重点看

1、初始开源-2009-11-10,go降生到了这个世界上

2、Go1.5 :2015,go开始用母语了,实现自举(Go 编译 Go),GC 延迟从 300ms 降至 30ms,奠定现代 Go 基础

3、Go1.11:2018,引入了Go Module解决了依赖问题,让现在的我都收益不止--今年2025

4、Go1.18:2022,泛型、模糊测试、工作区多模块,等均进行了新功能的填充与优化。这个咱暂接触不够多,后期接触了,会回来优化本篇博客

.....

目录
相关文章
|
5月前
|
缓存 人工智能 JSON
|
云栖大会 开发者
收到阿里云【乘风者计划】博主证书和奖励
收到阿里云【乘风者计划】博主证书和奖励 2023年2月对我来说是一个很好的开端,因为我在1号就收到了阿里云寄给我的【乘风者计划】博主证书和奖励。好兆头啊! 我收到的是我获得的【技术博主】【星级博主】【专家博主】三个的奖品和证书,一快给我寄过来哒!
3396 2
收到阿里云【乘风者计划】博主证书和奖励
|
5月前
|
运维 监控 Cloud Native
巨人网络《超自然行动组》携手阿里云打造云原生游戏新范式
通过 ACK(容器服务)、ESS(弹性伸缩)、网络型负载均衡 NLB、OpenKruiseGame(OKG)、SLS(日志服务)、ARMS(应用实时监控服务)、阿里云原生防护(Native Protection),以及云原生数据库 polardb 和 Redis 的深度协同,巨人网络构建了一套高弹性、高可用、低成本、智能化、高安全且高性能数据处理能力的新一代游戏基础设施,为行业树立了云原生落地的标杆。如今,随着日活跃用户(DAU)突破千万大关,这套技术体系,已经成为游戏行业“云原生转型”的标杆案例。
908 47
|
5月前
|
缓存 人工智能 Go
go.work
Go语言在1.18版本引入的go.work文件是一种工作区管理工具,用于简化多模块开发。它通过在项目根目录创建go.work文件,使用use指令关联本地模块路径,使开发者能够直接调用不同模块(如主应用myapp和共享库mylib)的函数,无需修改go.mod文件或发布未完成版本。相比replace机制,go.work提供了更便捷的本地开发方案,既能保持生产环境依赖完整性,又解决了多模块协同开发时版本不匹配的问题,尤其适合大型项目和微服务架构。
1301 2
|
5月前
|
存储 负载均衡 Java
GMP模型
本篇将会从起源、设计思想、设计策略、运行模式、现实场景多维度进行论述GMP。并在结尾补充了五个易混易错的要点
351 1
|
4月前
|
数据采集 算法 数据可视化
连续隐马尔可夫离散隐马尔科夫模型的MATLAB实现​
连续隐马尔可夫离散隐马尔科夫模型的MATLAB实现​
|
5月前
|
人工智能 安全 机器人
企业OpenClaw部署实践:基于阿里云无影一键部署方案
OpenClaw(原Clawdbot/Moltbot)是一款开源本地优先AI智能体平台,支持自然语言调用浏览器、邮件、文件等工具,自动处理文档、日程、邮件等任务。阿里云提供一键部署方案,尤其推荐无影云电脑版——集中管理、多端接入、7×24稳定运行、数据不出域、开箱即用。
624 15
|
5月前
|
算法
动态规划之完全背包
本文详解完全背包问题:作为动态规划经典题型,区别于01背包(每物限选1次),其特点是每种物品可无限次选取。文章从定义、状态转移方程(dp[i][j] = max(dp[i-1][j], dp[i][j-w]+v))、二维/一维实现到遍历顺序对组合数与排列数的影响,结合零钱兑换II、组合总和IV等5道典型例题深入剖析,助力掌握核心思想与编码技巧。
314 1
|
5月前
|
存储 缓存 安全
Go map 底层原理
虽然大家天天都在用 `map`,但很多人对它的理解只停在“查得快”“底层是哈希表”“桶里有 8 个槽位”这几句。或许跟别人吹牛的时候,还有几分用处;但真到线上排查延迟抖动、锁竞争、内存占用、热点键冲突,这点认识往往是不够的。
447 1
|
5月前
|
机器学习/深度学习 人工智能 算法
求最大公约数(gcd)与最小公倍数(lcm)【C/C++】
本博客详解最大公约数(GCD)的核心思想与多种解法:从基础概念出发,系统讲解辗转相除法(欧几里得算法)、更相减损术、质因数分解、穷举法及递归法,并配以图示、数学原理与可运行代码。最后通过“等差数列”实战题,展示GCD在算法题中的巧妙应用,强调数学本质理解对编程实现的关键作用。
1461 0