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)。 这些延迟调用将被添加到堆栈中。

 

go defer-stack

go defer-stack

 

上图表示添加 defer 调用后堆栈的内容。 堆栈是后进先出的数据结构。 最后压入栈的 defer 调用会先被拉出并执行。 在这种情况下, defer fmt.Printf("%c", 'm') 将首先执行,因此字符串将以相反的顺序打印。

上面程序的执行结果如下

go-stack-reverse-string


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 调用。

我们如果运行上面的程序,每次运行输出的结果的顺序是不同的

go no 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")

}

运行示例

上面程序的输出结果如下

go defer 示例

在上面的程序中使用 defer 还有一个好处。 假设我们使用新的 if 条件向 area 方法添加另一个返回路径。 如果对 wg.Done() 的调用没有延迟,我们必须小心并确保在这个新的返回路径中调用 wg.Done()。 但是由于对 wg.Done() 的调用被延迟,我们不必担心向此方法添加新的返回路径。

本文转载自:迹忆客(https://www.jiyik.com)

以上是 Go 语言 深入理解 defer关键字及defer实践 的全部内容, 来源链接: utcz.com/z/290234.html

回到顶部