go 语言 深入理解 defer关键字及defer实践
在 go 语言 defer(延迟) 关键字基本用法详解 这篇文章中,我们详细介绍了 defer关键字的作用以及通过简单示例来了解了defer函数的参数问题。
下面我们对defer进行一个比较深入的探讨
defer 的栈
当一个函数有多个 defer 调用时,它们会被压入堆栈并按后进先出 (lifo) 顺序执行。
我们的说明离不开代码,我们将编写一个小程序,它使用一堆 defers 反向打印一个字符串。
package main
import (
"fmt"
)
func main() {
name := "jiyik.com"
fmt.printf("original string: %s\n", string(name))
fmt.printf("reversed string: ")
for _, v := range []rune(name) {
defer fmt.printf("%c", v)
}
}
在上面的程序中, 使用 for range
循环迭代字符串并调用 defer fmt.printf("%c", v)
。 这些延迟调用将被添加到堆栈中。

上图表示添加 defer 调用后堆栈的内容。 堆栈是后进先出的数据结构。 最后压入栈的 defer 调用会先被拉出并执行。 在这种情况下, defer fmt.printf("%c", 'm')
将首先执行,因此字符串将以相反的顺序打印。
上面程序的执行结果如下
defer的实际使用
到目前为止,我们看到的代码示例没有展示 defer 的实际用途。 在本节中,我们将研究 defer 的一些实际用途。
defer 用于在不考虑代码流的情况下应该执行函数调用的地方。 让我们通过程序示例来理解这一点。 我们将首先编写不使用 defer 的程序,然后我们将修改它以使用 defer 并了解 defer 的用处。
package main
import (
"fmt"
"sync"
)
type rect struct {
length int
width int
}
func (r rect) area(wg *sync.waitgroup) {
if r.length < 0 {
fmt.printf("rect %v's length should be greater than zero\n", r)
wg.done()
return
}
if r.width < 0 {
fmt.printf("rect %v's width should be greater than zero\n", r)
wg.done()
return
}
area := r.length * r.width
fmt.printf("rect %v's area %d\n", r, area)
wg.done()
}
func main() {
var wg sync.waitgroup
r1 := rect{-67, 89}
r2 := rect{5, -67}
r3 := rect{8, 9}
rects := []rect{r1, r2, r3}
for _, v := range rects {
wg.add(1)
go v.area(&wg)
}
wg.wait()
fmt.println("all go routines finished executing")
}
在上面的程序中,我们创建了一个 rect
结构体。并在 rect 上定义了一个方法 area
,用来计算矩形的面积。 此方法检查矩形的长度和宽度是否小于零。 如果是,则打印相应的消息,否则打印矩形的面积。
main 函数创建了 3 个 rect 类型的变量 r1、r2 和 r3。 然后将它们添加到 rects 切片中。 然后使用 for range 循环迭代这个切片,并且 area 方法作为并发 goroutine 调用。 waitgroup wg
用于确保 main 函数被阻塞,直到所有 goroutines 执行完毕。 这个 waitgroup 作为参数传递给 area 方法,area 方法调用 wg.done()
。 通知主函数 goroutine 已完成其工作。 如果仔细观察,就会发现这些调用恰好在 area 方法返回之前发生。 wg.done() 应该在方法返回之前调用,而不管代码流采用的路径如何,因此这些调用可以有效地替换为单个 defer 调用。
我们如果运行上面的程序,每次运行输出的结果的顺序是不同的
让我们使用 defer 重写上面的程序。
在下面的程序中,我们删除了上面程序中的 3 个 wg.done()
调用,并将其替换为单个 defer wg.done() 调用。 这使得代码更简单易懂。
package main
import (
"fmt"
"sync"
)
type rect struct {
length int
width int
}
func (r rect) area(wg *sync.waitgroup) {
defer wg.done()
if r.length < 0 {
fmt.printf("rect %v's length should be greater than zero\n", r)
return
}
if r.width < 0 {
fmt.printf("rect %v's width should be greater than zero\n", r)
return
}
area := r.length * r.width
fmt.printf("rect %v's area %d\n", r, area)
}
func main() {
var wg sync.waitgroup
r1 := rect{-67, 89}
r2 := rect{5, -67}
r3 := rect{8, 9}
rects := []rect{r1, r2, r3}
for _, v := range rects {
wg.add(1)
go v.area(&wg)
}
wg.wait()
fmt.println("all go routines finished executing")
}
上面程序的输出结果如下
在上面的程序中使用 defer 还有一个好处。 假设我们使用新的 if 条件向 area 方法添加另一个返回路径。 如果对 wg.done() 的调用没有延迟,我们必须小心并确保在这个新的返回路径中调用 wg.done()。 但是由于对 wg.done() 的调用被延迟,我们不必担心向此方法添加新的返回路径。
转载请发邮件至 1244347461@qq.com 进行申请,经作者同意之后,转载请以链接形式注明出处
本文地址:
相关文章
在 javascript 中验证 google recaptcha 第 2 版
发布时间:2024/03/23 浏览次数:193 分类:javascript
-
本文演示了如何在 javascript 中验证 google recaptcha。
c# 中的 goto 语句
发布时间:2024/02/02 浏览次数:184 分类:编程语言
-
本教程演示了如何在 c# 中使用 goto 以及何时使用它会有所帮助本教程将演示如何在 c# 中使用 goto 语法,并提供一些代码中的实际使用示例。
发布时间:2023/12/20 浏览次数:197 分类:python
-
本文为你提供了 python 中是否存在 goto 语句的答案。本文为你提供了 python 中是否存在 goto 语句的答案。基本上,python 不支持 goto 语句。
避免 python中的 typeerror: input expected at most 1 argument, got 3 错误
发布时间:2023/07/08 浏览次数:671 分类:python
-
python 中避免 typeerror: input expected atmost 1 argument, got 3 error在python编程中,我们有两个内置方法来获取用户的输入:input(prompt)和 raw_input(prompt)。
使用 python 将文件上传到 google 云端硬盘
发布时间:2023/06/15 浏览次数:544 分类:python
-
本文将介绍我们如何使用 python 将文件上传到云盘,以 google drive 为例。 为此,我们将使用 google drive api。
发布时间:2023/05/30 浏览次数:293 分类:python
-
当我们在 numpy 中传递一维数组而不是二维数组时,会发生错误 valueerror: expected 2d array, got 1d array instead 。如您所知,每种编程语言都会遇到很多错误,有些是在运行时,有些是在编译时。 pyth