Go defer 特性和使用场景
golang 的 defer 语句用于延迟调用。defer 会在当前函数返回之前执行 defer 注册的函数。比如 defer func_defer() 这样语句会让你注册一个函数变量到 defer 的全局链表中,在 defer 语句所在的函数退出之前调用。
defer 可以代替其它语言中 try…catch… 语句,也可以用来处理释放资源等收尾操作,比如关闭文件句柄、关闭数据库连接等。defer 还能用于 panic 的 recovery。
1. defer 的特性
我们先深入的剖析下 defer 具有的特性,知其然也。这些特性是需要我们记住的特点,才能更好的理解 defer 使用的场景。
1) 延迟调用
package main
func main() {
defer println("--- defer ---")
println("--- end ---")
}
运行结果:
--- end ---
--- defer ---
defer 会在 main 函数所有语句之后, return 之前时候调用。核心要点:
- 延迟调用:defer 语句本身虽然是 main 的第一行,但是 println("--- end ---") 先打印的;
- defer 关键字一定是处于函数上下文:defer 必须放在函数内部;
2) LIFO
一个函数中含有有多个 defer,调用顺序采用压栈式执行,后入先出(LIFO)。
package main import (
"strconv"
) func main() {
for i := 1; i <= 3; i++ {
defer println("defer -->" + strconv.Itoa(i))
}
println("--- end ---")
}
压栈式执行,也就是说先注册的函数后调用。如上,我们注册的顺序式 1,2,3,最后打印 “--- end ---”,所以执行的结果自然是反着来的,程序输出:
--- end ---
defer -->3
defer -->2
defer -->1
3) 作用域
defer 只会和 defer 语句所在的特定函数绑定在一起,作用域也只在这个函数。 从语法上来讲,defer 语句也一定要在函数内,否则会报告语法错误。
package main
func main() {
func() {
defer println("--- defer ---")
}()
println("--- end ---")
}
如上,defer 处于一个匿名函数中,就 main 函数本身来讲,匿名函数 fun(){}() 先调用且返回,然后再调用 println("--- end ---") ,所以程序输出自然是:
--- defer ---
--- end ---
4) 异常场景
这个是非常重要的特性:panic 也能执行。golang 不鼓励异常的编程模式,但是却也留了 panic-recover 这个异常和捕捉异常的机制。所以 defer 机制就显得尤为重要,甚至可以说是必不可少的。因为你没有一个无视异常,永保调用的 defer 机制,很有可能就会发生各种资源泄露,死锁等场景。为什么?因为发生了 panic 却不代表进程一定会挂掉,很有可能被外层 recover 住。
package main
func main() {
defer func() {
if e := recover(); e != nil {
println("--- defer ---")
}
}()
panic("throw panic")
}
如上,main 函数注册一个 defer ,且稍后主动触发 panic,main 函数退出之际就会调用 defer 注册的匿名函数。再提一点,这里其实有两个要点:
- defer 在 panic 异常场景也能确保调用;
- recover 必须和 defer 结合才有意义;
2. defer 使用场景
1) 并发同步
以下的例子对两个并发的协程做了下同步控制,常规操作。
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// 程序逻辑
}()
}
wg.Wait()
2) 锁场景
加锁解锁必须配套,在 golang 有了 defer 之后,你就可以写了 lock 之后,立马就写 unlock ,这样就永远不会忘了。
mu.RLock()
defer mu.RUnlock()
但是请注意,lock 以下的代码都会在锁内。所以下面的代码要足够精简和快速才行,如果说下面的逻辑很复杂,那么可能就需要手动控制 unlock 防止的位置了。
3) 资源释放
某些资源是临时创建的,作用域只存在于现场函数中,用完之后需要销毁,这种场景也适用 defer 来释放。释放就在创建的下一行,这是个非常好的编程体验,这种编程方式能极大的避免资源泄漏。因为写了创建立马就可以写释放了,再也不会忘记了。
// 创建一个客户端 client;
cli, err := clientv3.New(clientv3.Config{Endpoints: endpoints})
if err != nil {
log.Fatal(err)
}
// 释放该 client ,也就是说该 client 的声明周期就只在该函数中;
defer cli.Close()
4) panic-recover
recover 必须和 defer 结合才行,使用姿势一般如下:
defer func() {
if v := recover(); v != nil {
_ = fmt.Errorf("PANIC=%v", v)
}
}()
3. 总结
- defer 其实并不是 golang 独创,是多种高级语言的共同选择;
- defer 最重要的一个特点就是无视异常可执行,这个是 golang 在提供了 panic-recover 机制之后必须做的补偿机制;
- defer 的作用域存在于函数,defer 也只有和函数结合才有意义;
- defer 允许你把配套的两个行为代码放在最近相邻的两行,比如创建&释放资源、加锁&释放锁等,使得代码更易读,编程体验优秀。
参考资料
1. go语言编程
2. 编程宝库
Go defer 特性和使用场景的更多相关文章
- Redis高级特性及应用场景
Redis高级特性及应用场景 redis中键的生存时间(expire) redis中可以使用expire命令设置一个键的生存时间,到时间后redis会自动删除它. 过期时间可以设置为秒或者毫秒精度. ...
- 二、RabbitMQ 进阶特性及使用场景 [.NET]
前言 经过上一篇的介绍,相信大家对RabbitMQ 的各种概念有了一定的了解,及如何使用RabbitMQ.Client 去发送和消费消息. 特性及使用场景 1. TTL 过期时间 TTL可以用来指定q ...
- HBase篇(1)-特性与应用场景
[每日五分钟搞定大数据]系列,HBase第一篇 结束了Zookeeper篇, 接下来我们来说下Google三驾马车之一BigTable的开源实现:HBase,要讲的内容暂定如下: 这是第一篇我们先不聊 ...
- redis特性,使用场景
redis特性: 1.redis保存在内存中,读写速度快. 2.redis--持久化(断电数据不丢失:对数据的更新将异步保存到磁盘上). 3.redis数据结构丰富 4.redis功能丰富 5.简单( ...
- BFC的特性及使用场景
BFC(Block Formatting Context)块级格式化上下文,是Web页面 CSS 视觉渲染的一部分,用于决定块盒子的布局及浮动相互影响范围的一个区域. BFC的特性: 1. 属于同一个 ...
- redis特性与使用场景
一.8大特性 1.速度快 数据存储在内存,可达到10万OPS 2.可持久化,断电不丢数据 所有数据保存在内存中,对数据的更新异步的保存在硬盘中 3.多种数据结构 字符串.哈希.列表.集合.有序集 合位 ...
- Redis特性和应用场景
Redis特性 速度快 Redis使用标准C编写实现,而且将所有数据加载到内存中,所以速度非常快.官方提供的数据表明,在一个普通的Linux机器上,Redis读写速度分别达到81000/s和11000 ...
- Chrome 插件特性及实战场景案例分析
一.前言 提起Chrome扩展插件(Chrome Extension),每个人的浏览器中或多或少都安装了几个插件,像一键翻译.广告屏蔽.录屏等等,通过使用这些插件,可以有效的提高我们的工作效率:但有时 ...
- 面试官:Kafka是什么,它有什么特性与使用场景?
哈喽!大家好,我是小奇,一位热爱分享的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 不知不觉进入了五月份了,天气越 ...
随机推荐
- P6076-[JSOI2015]染色问题【组合数学,容斥】
正题 题目链接:https://www.luogu.com.cn/problem/P6076 题目大意 给出\(n*m\)的网格,\(c\)种颜色涂色要求 每个格子可以染色也可以不染 每一行每一列至少 ...
- The Data Way Vol.2 | 做个『单纯』的程序员还真不简单
关于「The Data Way」 「The Data Way」是由 SphereEx 公司出品的一档播客节目.这里有开源.数据.技术的故事,同时我们关注开发者的工作日常,也讨论开发者的生活日常:我们聚 ...
- Java基础之(三):IDEA的安装及破解
IDEA的安装 IDEA官网:IDEA 点击IJ 找好操作系统,点击下载 双击打开,自己找个安装路径 勾选这两个即可 旗舰版破解及汉化 上面是个人社区版,是免费的,但是如果想要使用汉化版的,需要寻找插 ...
- jenkins容器内安装python3
前言 很多小伙伴可能在考虑 jenkins 拉取了 github 上的代码后,发现还越少 python3 环境,那能怎么办呢? 咨询了一位运维朋友给我的答案是,将 python3 挂载到容器工作目录上 ...
- Java(44)JDK新特性之函数式接口
作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201667.html 博客主页:https://www.cnblogs.com/testero ...
- CAD网页Web端显示开发为什么要以WebGIS的思路来开发?
背景 在之前的博文CAD图DWG解析WebGIS可视化技术分析总结中讲解了如何把CAD的DWG格式的图纸Web可视化的方案.博文发布后,受到不少同行们的关注,也有不少咨询一些专业问题,其中大家可能疑惑 ...
- K12教培从业者转型指南 换个赛道依然可以再创辉煌
随着"双减"政策的落地,属于K12教培机构的时代逐渐拉上帷幕,面对机会不再的K12教培行业,约70万机构和近千万的从业人员面临转型问题.压力之下,留下或离开?对广大K12教培机构从 ...
- flask 之 请求钩子
请求钩子 什么是请求钩子? 在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要统一处理,为了让每个视图函数避免编写重复功能的代码,flask提供了统一的接口可以添加这些处理函数,即请求钩子. f ...
- Redis分布式锁的正确实现方式[转载]
前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...
- 【UE4 C++】Tick的三种方式、异步蓝图节点
Tick的三种方式 包括 默认 Tick (Actor.Component.UMG) TimerManager 定时器 FTickableGameObject 可以写原生 Object 也可以继承UO ...