@phper
2018-03-13T18:07:03.000000Z
字数 4016
阅读 2718
Golang
原文:https://golangbot.com/defer/
欢迎访问Golang 系列教程中的第29章。
Defer语句用于在返回defer语句的函数之前执行函数调用。这个定义可能看起来很复杂,但通过一个例子很容易理解。
package main
import (
"fmt"
)
func finished() {
fmt.Println("Finished finding largest")
}
func largest(nums []int) {
defer finished()
fmt.Println("Started finding largest")
max := nums[0]
for _, v := range nums {
if v > max {
max = v
}
}
fmt.Println("Largest number in", nums, "is", max)
}
func main() {
nums := []int{78, 109, 2, 563, 300}
largest(nums)
}
在playground上运行
上面是一个简单的程序用来找出一个切片数组中的最大值。largest
函数采用int切片
作为参数, 并打印输入切片的最大值。largest
函数的第一行包含语句defer finished()
。这意味着finished()
函数将在largest
函数返回之前调用 (也就是largest函数全部执行完成后,再去执行finished())。运行此程序, 您可以看到以下输出打印。
Started finding largest
Largest number in [78 109 2 563 300] is 563
Finished finding largest
largest
函数开始执行并打印上面的前两行。在它返回之前, 我们的延迟函数finished
执行并打印文本Finished finding largest
Defer 并不仅仅限于函数(function)。延迟方法(method)调用也是合法的。我们来编写一个小程序来测试它。
package main
import (
"fmt"
)
type person struct {
firstName string
lastName string
}
func (p person) fullName() {
fmt.Printf("%s %s",p.firstName,p.lastName)
}
func main() {
p := person {
firstName: "John",
lastName: "Smith",
}
defer p.fullName()
fmt.Printf("Welcome ")
}
在playground上运行
在上面的程序中, 我们推迟了22行的方法调用。其余的程序是自我解释。这个程序输出:
Welcome John Smith
defer执行语句时会计算延迟函数的参数,而不是实际函数调用完成时的参数。
让我们通过一个例子来理解这一点。
package main
import (
"fmt"
)
func printA(a int) {
fmt.Println("value of a in deferred function", a)
}
func main() {
a := 5
defer printA(a)
a = 10
fmt.Println("value of a before deferred function call", a)
}
上面的程序中, 第11行a
的最初值为5。当延迟语句在12行中执行时, a
的值为 5, 因此这将是延迟printA
函数的参数。我们将a
的值更改为13行中的10。下一行将打印a
的值。这个程序输出,
value of a before deferred function call 10
value of a in deferred function 5
从上面的输出可以理解, 尽管在执行延迟语句后a
的值更改为10
, 但实际的延迟函数调用printA(a)
仍然打印5
.
当函数有多个延迟调用时, 它们将添加到堆栈上, 并在最后一个 "先出" (LIFO) 顺序中执行。
我们将编写一个小程序, 打印一个字符串反向使用一堆延迟。
package main
import (
"fmt"
)
func main() {
name := "Naveen"
fmt.Printf("Orignal String: %s\n", string(name))
fmt.Printf("Reversed String: ")
for _, v := range []rune(name) {
defer fmt.Printf("%c", v)
}
}
在上面的程序中,for range
循环位于11行, 循环访问该字符串并调用defer fmt.Printf("%c", v)
。这些延迟调用将被添加到堆栈中, 最后以先出顺序执行, 因此字符串将以相反的顺序打印。这个程序将输出,
Orignal String: Naveen
Reversed String: neevaN
到目前为止, 我们看到的代码示例不显示延迟的实际使用。在本节中, 我们将研究Defer的一些实际用途。
延迟用于在执行函数调用的地方, 而不管代码流如何。让我们以使用WaitGroup
的程序的示例来理解这一点。我们将先编写程序而不使用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")
}
在上面的程序中, 我们在第8行创建了一个矩形结构, 并且在13行的rect
上的方法area
中计算了矩形的面积。此方法检查矩形的长度和宽度是否小于零。如果是这样, 它将打印相应的消息, 否则它将打印矩形区域。
main
函数创建了3个类型为rect
的变量r1
、 r2
和r3
。然后将它们添加到第34行中的rects
切片中。然后使用for range
此切片进行迭代, 并将 range
方法称为37行中的并发area
Goroutine 。WaitGroup wg
用于确保主函数被阻止, 直到所有 Goroutines 完成执行为止。此 WaitGroup
作为参数传递给区域方法, 并且区域方法调用wg.Done()
在16、21和26行中, 通知主要功能 Goroutine 完成其工作。如果您注意到, 则可以看到这些调用恰好在area
方法返回之前, 应调用 wg.Done()
, 而不考虑代码流所采用的路径, 因此这些调用可以通过一个defer调用有效替换.
让我们用延迟重写上面的程序。
在下面的程序中, 我们删除了3个地方的wg.Done()
调用, 并在第14行用调用单个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")
}
这个程序输出,
rect {8 9}'s area 72
rect {-67 89}'s length should be greater than zero
rect {5 -67}'s width should be greater than zero
All go routines finished executing
在上面的程序中使用延迟还有一个优点。让我们说, 我们使用新的if条件添加另一个返回路径到area
方法。如果调用wg.Done()
未延迟, 我们必须小心, 并确保我们调用wg.Done()
在此新的返回路径中。但自调用wg.Done()
是 defered, 我们不必担心向此方法添加新的返回路径。
这使我们到本教程的末尾。祝你今天开心。