关于Go defer的详细使用
先抛砖引玉defer的延迟调用:
defer特性:
. 关键字 defer 用于注册延迟调用。
. 这些调用直到 return 前才被执。因此,可以用来做资源清理。
. 多个defer语句,按先进后出的方式执行。
. defer语句中的变量,在defer声明时就决定了。
defer用途:
. 关闭文件句柄
. 锁资源释放
. 数据库连接释放
好,废话不多说,实例加深理解,我们先看看一段代码
package main import "fmt" func main() {
var users []struct{}
for i := range users {
defer fmt.Println(i)
}
}
输出:4 3 2 1 0 ,defer 是先进后出,这个输出没啥好说的。
我们把上面的代码改下:
defer 换上闭包:
package main import "fmt" func main() {
var users []struct{}
for i := range users {
defer func() { fmt.Println(i) }()
}
}
输出:4 4 4 4 4,很多人也包括我。预期的结果不是 4 3 2 1 0 吗?官网对defer 闭包的使用大致是这个意思:
函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4,所以输出全都是4。那么 如何正常输出预期的 4 3 2 1 0 呢?
不用闭包,换成函数:
package main import "fmt" func main() {
var users []struct{}
for i := range users {
defer Print(i)
}
}
func Print(i int) {
fmt.Println(i)
}
函数正常延迟输出:4 3 2 1 0。
我们再举一个可能一不小心会犯错的例子:
defer调用引用结构体函数
package main import "fmt" type Users struct {
name string
} func (t *Users) GetName() { // 注意这里是 * 传地址 引用Users
fmt.Println(t.name)
}
func main() {
list := []Users{{"乔峰"}, {"慕容复"}, {"清风扬"}}
for _, t := range list {
defer t.GetName()
}
}
输出:清风扬 清风扬 清风扬。
这个输出并不会像我们预计的输出:清风扬 慕容复 乔峰
可是按照前面的go defer函数中的使用说明,应该输出清风扬 慕容复 乔峰才对啊?
那我们换一种方式来调用一下
package main import "fmt" type Users struct {
name string
} func (t *Users) GetName() { // 注意这里是 * 传地址 引用Users
fmt.Println(t.name)
}
func GetName(t Users) { // 定义一个函数,名称自定义
t.GetName() // 调用结构体USers的方法GetName
}
func main() {
list := []Users{{"乔峰"}, {"慕容复"}, {"清风扬"}}
for _, t := range list {
defer GetName(t)
}
}
输出:清风扬 慕容复 乔峰。
这个时候输出的就是所谓"预期"滴了
当然,如果你不想多写一个函数,也很简单,可以像下面这样(改2处),同样会输出清风扬 慕容复 乔峰
package main import "fmt" type Users struct {
name string
} func (t *Users) GetName() { // 注意这里是 * 传地址 引用Users
fmt.Println(t.name)
}
func GetName(t Users) { // 定义一个函数,名称自定义
t.GetName() // 调用结构体USers的方法GetName
}
func main() {
list := []Users{{"乔峰"}, {"慕容复"}, {"清风扬"}}
for _, t := range list {
t2 := t // 定义新变量t2 t赋值给t2
defer t2.GetName()
}
}
输出:清风扬 慕容复 乔峰。
通过以上例子
我们可以得出下面的结论:
defer后面的语句在执行的时候,函数调用的参数会被保存起来,但是不执行。也就是复制了一份。但是并没有说struct这里的*指针如何处理,
通过这个例子可以看出go语言并没有把这个明确写出来的this指针(比如这里的* Users)当作参数来看待。到这里有滴朋友会说。看似多此一举的声明,
直接去掉指针调用 t *Users改成 t Users 不就行了?
package main import "fmt" type Users struct {
name string
} func (t Users) GetName() { // 注意这里是 * 传地址 引用Users
fmt.Println(t.name)
} func main() {
list := []Users{{"乔峰"}, {"慕容复"}, {"清风扬"}}
for _, t := range list {
defer t.GetName()
}
}
输出:清风扬 慕容复 乔峰。这就回归到上面的 defer 函数非引用调用的示例了。所以这里我们要注意defer后面的指针函数和普通函数的调用区别。很容易混淆出错。
多个 defer 注册,按 FILO 次序执行 ( 先进后出 )。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行,我们看看这一段
package main func users(i int) {
defer println("北丐")
defer println("南帝") defer func() {
println("西毒")
println( / i) // 异常未被捕获,逐步往外传递,最终终止进程。
}() defer println("东邪")
} func main() {
users()
println("武林排行榜,这里不会被输出哦")
}
输出:
东邪
西毒
南帝
北丐
panic: runtime error: integer divide by zero
goroutine [running]:
main.users.func1(0x0)
我们发现函数中异常,最后才捕获输出,但是一旦捕获了异常,后面就不会再执行了,即终止了程序。
*延迟调用参数在求值或复制,指针或闭包会 "延迟" 读取。
package main func test() {
x, y := "乔峰", "慕容复" defer func(s string) {
println("defer:", s, y) // y 闭包引用 输出延迟和的值,即y+= 后的值=慕容复第二
}(x) // 匿名函数调用,传送参数x 被复制,注意这里的x 是 乔峰,而不是下面的 x+= 后的值 x += "第一"
y += "第二"
println("x =", x, "y =", y)
} func main() {
test()
}
输出:
x = 乔峰第一
y = 慕容复第二
defer: 乔峰 慕容复第二
defer 与 return注意
package main import "fmt" func Users() (s string) { s = "乔峰"
defer func() {
fmt.Println("延迟执行后:"+s)
}() return "清风扬"
} func main() {
Users() // 输出:延迟执行后:清风扬
}
解释:在有命名返回值的函数中(这里命名返回值为 s),执行 return "风清扬" 的时候实际上已经将s 的值重新赋值为 风清扬。
所以defer 匿名函数 输出结果为 风清扬 而不是 乔峰。
在错误的位置使用 defer,来一段不严谨滴代码:
package main import "net/http" func request() error {
res, err := http.Get("http://www.google.com") // 不翻墙的情况下。是无法访问滴
defer res.Body.Close()
if err != nil {
return err
} // ..继续业务code... return nil
} func main() {
request()
}
输出:
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x40 pc=0x5e553e]
Why?因为在这里我们并没有检查我们的请求是否成功执行,当它失败的时候,我们访问了 Body 中的空变量 res ,所以会抛出异常。
怎么优化呢?
我们应该总是在一次成功的资源分配下面使用 defer ,简单点说就是:当且仅当 http.Get 成功执行时才使用 defer.
package main import "net/http" func request() error {
res, err := http.Get("http://www.google.com")
if res != nil {
defer res.Body.Close()
} if err != nil {
return err
} // ..继续业务code... return nil
} func main() {
request()
}
这样,当有错误的时候,err 会被返回,否则当整个函数返回的时候,会关闭 res.Body 。
解释:在这里,同样需要检查 res 的值是否为 nil ,这是 http.Get 中的一个警告。
通常情况下,出错的时候,返回的内容应为空并且错误会被返回,可当你获得的是一个重定向 error 时, res 的值并不会为 nil ,
但其又会将错误返回。所以上面的代码保证了无论如何 Body 都会被关闭。
另外我们再聊下关于文件的defer close。在这里,f.Close() 可能会返回一个错误,可这个错误会被我们忽略掉
我们看一段代码:
package main import "os" func open() error {
f, err := os.Open("result.json") // 确保文件名存在
if err != nil {
return err
} if f != nil {
defer f.Close()
} // ..code... return nil
} func main() {
open()
}
表面上看似没问题,其实f.Close可能关闭文件失败,我们优化下:
package main import "os" func open() error {
f, err := os.Open("result.json")
if err != nil {
return err
} if f != nil {
defer func() {
if err := f.Close(); err != nil {
return
}
}()
} // ..code... return nil
} func main() {
open()
}
如果有代码洁癖优化强迫症滴,哈哈。这里我们还可以优化下,可以通过命名的返回变量来返回 defer 内的错误。 如下:
package main import "os" func open() (err error) {
f, err := os.Open("result.json")
if err != nil {
return err
} if f != nil {
defer func() {
if ferr := f.Close(); ferr != nil {
err = ferr //这里 通过命名的返回变量ferr赋值给err 来返回 defer 内的错误
}
}()
} // ..code... return nil
} func main() {
open()
}
最后一个容易忽视的问题:如果你尝试使用相同的变量释放不同的资源,那么这个操作可能无法正常执行
神马意思?继续看:
package main import (
"fmt"
"os"
) func open() error {
f, err := os.Open("result.json")
if err != nil {
return err
}
if f != nil {
defer func() {
if err := f.Close(); err != nil {
fmt.Printf("延迟关闭文件result.json 错误 %v\n", err)
}
}()
} // ..code... f, err = os.Open("result2.json")
if err != nil {
return err
}
if f != nil {
defer func() {
if err := f.Close(); err != nil {
fmt.Printf("延迟关闭文件result2.json 错误 %v\n", err)
}
}()
} return nil
} func main() {
open()
}
输出:
延迟关闭文件result.json 错误 close result2.json: file already closed
结论:当延迟函数执行时,只有最后一个变量会被用到,因此,f 变量 会成为最后那个资源 (result2.json)。
而且两个 defer 都会将这个资源作为最后的资源来关闭,也就是优先关闭了result2.json后,再执行第一个defer Close result1.json的时候,
其实还是在关闭result2.json.这样重复关闭同一个文件导致错误异常。肿么解决?很好办?用io.Closer属性
package main import (
"fmt"
"io"
"os"
) func open() error {
f, err := os.Open("result.json")
if err != nil {
return err
}
if f != nil {
defer func(f io.Closer) { // 注意修改滴地方
if err := f.Close(); err != nil {
fmt.Printf("延迟关闭文件result.json 错误 %v\n", err)
}
}(f) // 注意修改滴地方
} // ..code... f, err = os.Open("result2.json")
if err != nil {
return err
}
if f != nil {
defer func(f io.Closer) {// 注意修改滴地方
if err := f.Close(); err != nil {
fmt.Printf("延迟关闭文件result2.json 错误 %v\n", err)
}
}(f)// 注意修改滴地方
} return nil
} func main() {
open()
}
到此,关于Go中defer的使用总结到这里了,有更多的使用技巧或坑,欢迎诸位博友留言指正。。。。
关于Go defer的详细使用的更多相关文章
- defer和async的详细区别
看过javascript高级程序设计的人,在javascript高级程序设计里,应该看到了介绍了有关defer和async的区别,可是比较浅显,而且也说得不是很清楚.下面我们来通过图片来详细了解下df ...
- 【Go入门教程3】流程(if、goto、for、switch)和函数(多个返回值、变参、传值与传指针、defer、函数作为值/类型、Panic和Recover、main函数和init函数、import)
这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和 ...
- go语言文件操作,这期资料比较详细( 欢迎加入go语言群: 218160862 )
go语言文件操作,这期资料比较详细 欢迎加入go语言群: go语言深圳群 golang深圳 218160862 点击加入 文件操作 func Open(name string) (file *File ...
- Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- script 有哪个属性可以让它不立即执行 defer,async
.async 和 defer 属性 http://blog.csdn.net/qq_34986769/article/details/52155871 1. defer 属性<script sr ...
- Golang入门教程(十三)延迟函数defer详解
前言 大家都知道go语言的defer功能很强大,对于资源管理非常方便,但是如果没用好,也会有陷阱哦.Go 语言中延迟函数 defer 充当着 try...catch 的重任,使用起来也非常简便,然而在 ...
- HTML <script> 标签的 defer 和 async 属性
HTMKL <script>标签中有defer和async属性,简单介绍一下两者的区别吧. 普通的script标签会让浏览器立即下载并执行完毕,执行也是按照先后顺序,再进行后面的解析. ...
- Golang错误处理函数defer、panic、recover、errors.New介绍
在默认情况下,当发生错误(panic)后,程序就会终止运行 如果发生错误后,可以捕获错误,并通知管理人员(邮件或者短信),程序还可以继续运行,这当然无可厚非 errors.New("错误信息 ...
- 浏览器环境下JavaScript脚本加载与执行探析之defer与async特性
defer和async特性相信是很多JavaScript开发者"熟悉而又不熟悉"的两个特性,从字面上来看,二者的功能很好理解,分别是"延迟脚本"和"异 ...
随机推荐
- 如何学好javascript
今天逛论坛时看到有朋友问,是否有专门教Javascript的学校,这里想想把自己的一点建议和自己3年来的前端Javascript开发的经验跟大家分享下,也给出几本个人认为不错的书来做为大家学习的参考资 ...
- java中的时区转换
目录 java中的时区转换 一.时区的说明 二.时间的表示 三.时间戳 四.Date类和时间戳 五.java中的时区转换 java中的时区转换 一.时区的说明 地球表面按经线从东到西,被划成一个个区域 ...
- 百度地图Javascript API 调用示例
调用示例 !<!DOCTYPE html> <html> <head> <title>百度地图DEMO</title> </head& ...
- Java 方法重载 (Overload)
对重载 (Overload) 的认识 为什么要用方法重载: 对于功能类似的方法来说,因为参数列表不一样,如果定义不同名称的方法,太麻烦且难以记忆. 为了解决这个问题,引入方法的重载. 重载的定义: 多 ...
- [考试反思]1013csp-s模拟测试72:距离
最近总是这个样子. 看上去排名好像还可以,但是实际上离上面的分差往往能到80分,但是身后的分差其实只有10/20分. 比上不足,比下也不怎么的. 所以虽然看起来没有出rank10,但是在总分排行榜上却 ...
- 智和网管平台SugarNMS助力网络安全运维等保2.0建设
智和信通智和网管平台SugarNMS结合<信息安全技术 网络安全等级保护基本要求>(GB/T 22239-2019)等国家标准文件以及用户提出的网络安全管理需求进行产品设计,推出“监控+展 ...
- 2684亿!阿里CTO张建锋:不是任何一朵云都撑得住双11
2019天猫双11 成交额2684亿! "不是任何一朵云都能撑住这个流量.中国有两朵云,一朵是阿里云,一朵叫其他云."11月11日晚,阿里巴巴集团CTO张建锋表示,"阿里 ...
- 「Usaco2012 Dec」第一(字典树+拓扑排序)
(我恨字符串) 惯例化简题目:给定n个字符串,可以改变字符的相对大小(在字典序中的大小),问:字符串i是否能成为最小的字符串(字典序) 解题过程: 首先你可以预处理出来26的全排列然后暴力然后你只要用 ...
- ArcGIS API For Javascript :如何动态生成 token 加载权限分配的地图服务?
一.需求 项目中我们通常会遇到为外协团队.合作友商提供地图服务的需求,因此对地图服务的权限需要做出分配. 二.现状 主流的办法是用用户和角色来控制,通常使用代理方式和用户名密码的方式来实现. 三.思路 ...
- supervisor服务
描述: 遇到各种各样的各种坑, 可以通过python2 的pip安装, 可以通过apt安装, 不支持python3: 如若用apt安装可能会自动启动并且加入开机自启(不保证成功),pip安装一定不会需 ...