Go 语言 深入理解 defer关键字及defer实践
在 Go 语言 defer(延迟) 关键字基本用法详解 这篇文章中,我们详细介绍了 defer关键字的作用以及通过简单示例来了解了defer函数的参数问题。
下面我们对defer进行一个比较深入的探讨
defer 的栈
当一个函数有多个 defer 调用时,它们会被压入堆栈并按后进先出 (LIFO) 顺序执行。
我们的说明离不开代码,我们将编写一个小程序,它使用一堆 defers 反向打印一个字符串。
package main
import (
"fmt"
)
funcmain() {
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()
}
funcmain() {
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)
}
funcmain() {
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() 的调用被延迟,我们不必担心向此方法添加新的返回路径。
本文转载自:迹忆客(https://www.jiyik.com)
以上是 Go 语言 深入理解 defer关键字及defer实践 的全部内容, 来源链接: utcz.com/z/290234.html