Go中defer的5 个坑-第一部分

首发于:https://studygolang.com/articles/12061

Go 中 defer 的 5 个坑 - 第一部分

通过本节的学习以避免掉入基础的 defer 陷阱中

本文只适合想要进阶学习 Golang 的新手阅读,大牛请绕道。

#1 — defer nil 函数

如果一个延迟函数被赋值为 , 运行时的 异常会发生在外围函数执行结束后而不是 的函数被调用的时候。

例子

输出结果

发生了什么?

名为 func 的函数一直运行至结束,然后 函数会被执行且会因为值为 而产生 异常。然而值得注意的是, 的声明是没有问题,因为在外围函数运行完成后它才会被调用。

上面只是一个简单的案例,但同样的案例也可能发生在真实世界中,所以如果你遇上的话,可以想想是不是掉进了这个坑里。

#2 — 在循环中使用 defer

切忌在循环中使用 ,除非你清楚自己在做什么,因为它们的执行结果常常会出人意料。

但是,在某些情况下,在循环中使用 会相当方便,例如将函数中的递归转交给 ,但这显然已经不是本文应该讲解的内容。

在上面的例子中, 在循环中的延迟函数会在函数结束过后运行,而不是每次 for 循环结束之后。这些延迟函数会不停地堆积到延迟调用栈中,最终可能会导致一些不可预知的问题。

解决方案 #1:

不使用 ,直接在末尾调用。

解决方案 #2:

将任务转交给另一个函数然后在里面使用 ,在下面这种情况下,延迟函数会在每次匿名函数执行结束后执行。

进行基准测试

查看代码

#3 — 延迟调用含有闭包的函数

有时出于某种缘由,你想要让那些闭包延迟执行。例如,连接数据库,然后在查询语句执行过后中断与数据库的连接。

例子

运行一下

输出结果

竟然出问题了?

最终 并没有输出,最后只有 ,这是一个 bug,最终的情况是 执行结束后,其执行域得以被保存起来,但内部的闭包并不会被执行。

解决方案

稍作修改后, 返回了一个函数,然后我们再对这个函数使用 就能够在 执行结束后断开与数据库的连接。

输出结果

糟糕的处理方式:

即便这种处理方式很糟,但我还是想告诉你如何不用变量来解决这个问题,因此,我希望你能以此来了解 defer 亦或是 go 语言的运行机制。

这段代码从技术层面上说与上面的解决方案没有本质区别。其中,第一个圆括号是连接数据库(在 中立即执行的部分),然后第二个圆括号是为了在 结束时延迟执行断开连接的函数(也就是返回的闭包)。

归因于 创建了一个闭包类型的值,然后再使用 声明闭包函数, 的值需要被实现计算出来以便让 知道需要延迟哪个函数,这与 不直接相关但也可能帮助你解决一些问题。

#4 — 在执行块中使用 defer

你可能想要在执行块执行结束后执行在块内延迟调用的函数,但事实并非如此,它们只会在块所属的函数执行结束后才被执行,这种情况适用于所有的代码块除了上文的函数块例如,for,switch 等。

因为:延迟是相对于一个函数而非一个代码块

例子

输出结果

上例的延迟函数只会在函数执行结束后运行,而不是紧接着它所在的块(花括号内包含 defer 调用的区域)后执行,就像代码中的演示的那样,你可以使用花括号创造单独的执行块。

另一个解决方案

如果你希望在另一个块中使用 ,可以使用匿名函数(正如在第二个坑中我们采用的解决方案)。

#5 — 延迟方法的坑

同样,你也可以使用 来延迟 方法 调用,但也可能出一些岔子。

没有使用指针作为接收者

输出结果

使用指针对象作为接收者

输出结果

为什么会这样?

我们需要记住的是,当外围函数还没有返回的时候,Go 的运行时就会立刻将传递给延迟函数的参数保存起来。

因此,当一个以值作为接收者的方法被defer修饰时,接收者会在声明时被拷贝(在这个例子中那就是Car对象),此时任何对拷贝的修改都将不可见(例中的Car.model),因为,接收者也同时是输入的参数,当使用defer修饰时会立刻得出参数的值(也就是 “DeLorean DMC-12” )。

在另一种情况下,当被延迟调用时,接收者为指针对象,此时虽然会产生新的指针变量,但其指向的地址依然与上例中的 “c” 指针的地址相同。因此,任何修改都会完美地作用在同一个对象中。

以上就是本文的全部内容,我会在后续的文章中补充更多类似的坑 — 已经有至少 15 个易犯的 defer 错误榜上有名,如果你有任何想法,欢迎在下面留言。

via: https://blog.learngoprogramming.com/gotchas-of-defer-in-go-1-8d070894cb01

作者:Inanc Gumus

译者:yujiahaol68

校对:rxcai

本文由 GCTT 原创编译,Go 中文网 荣誉推出

本文来自企鹅号 - Go语言中文网媒体

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Pythonista

python之字符编码的重要思想

        unicode----->encode-------->utf-8

13720
来自专栏Python专栏

Python | 19个优雅的Python编程技巧

http://lovesoo.org/pythonic-python-programming.html

35230
来自专栏鸿的学习笔记

Python写的Python解释器(一)

导论 Byterun是一个用Python实现的Python解释器。它的结构类似于CPython(Python的主流实现方式)。

18520
来自专栏Albert陈凯

Stack and Heap 堆和栈的区别include

在和计算机内存打交道时,我们一定会碰到堆和栈,这两个东西很容易搞混,那么现在就来梳理一下二者的关系。 栈(Stack)是用来静态分配内存的而堆是动态分配内存的,...

28680
来自专栏Java后端技术

通俗易懂详解Java代理及代码实战

代理模式是Java常用的设计模式之一,实现代理模式要求代理类和委托类(被代理的类)具有相同的方法(提供相同的服务),代理类对象自身并不实现真正的核心逻辑,而是...

14210
来自专栏从零开始学 Web 前端

C/C++练习题(三)

分析:在我们看来,虽然使用字符数组和字符指针差不多,printf都可以打印出字符串出来,但是编译器对他们的处理完全不同。 对于字符指针,编译器看到后,...

97240
来自专栏PHP在线

48条高效率的PHP优化写法

1 字符串 1.1 少用正则表达式 能用PHP内部字符串操作函数的情况下,尽量用他们,不要用正则表达式, 因为其效率高于正则。 没得说,正则最耗性能。 str_...

780110
来自专栏微信终端开发团队的专栏

C# 内存管理机制及 WP 内存泄漏定位方法

C#内存管理机制及WP内存泄漏定位方法 一、C#的内存管理机制 1. 托管资源与非托管资源 什么是托管资源?托管资源通俗的理解就是,把资源交给.net去管理,这...

45770
来自专栏申龙斌的程序人生

零基础学编程008:print语句

在《零基础学编程007:FOR循环》这一篇文章里,我们只写了两行代码: for i in [1,2,3,4,5] : print( "(1+0.01)...

30570
来自专栏程序员宝库

48 条高效率的 PHP 优化写法

能用PHP内部字符串操作函数的情况下,尽量用他们,不要用正则表达式, 因为其效率高于正则。

25940

扫码关注云+社区

领取腾讯云代金券