在Golang中使用递延函数的实例

89 阅读1分钟

递延函数只有在包含defer 语句的主函数完成后才会被执行。这包括正常退出、恐慌和异常退出,所以延迟函数总是在最后被执行。延迟函数最常见的使用情况是在主控制流之后进行打开/关闭、连接/断开、锁定/解锁等类型的操作。请看下面的例子。

例子

func main() {
	fmt.Println("main: begin")
	fmt.Println("main: 1")

	defer child()

	fmt.Println("main: 2")
	fmt.Println("main: end")
}

func child() {
	fmt.Println("child: begin")
	fmt.Println("child: 1")
	fmt.Println("child: end")
}

// main: begin
// main: 1
// main: 2
// main: end
// child: begin
// child: 1
// child: end

如下图所示,所有的延迟函数都会被执行,但无论它们被调用多少次,都是以相反的顺序:

func main() {
	defer child(0)
	fmt.Println("main: begin")

	defer child(1)
	fmt.Println("main: middle")
	defer child(2)

	fmt.Println("main: end")
	defer child(3)
}

func child(i int) {
	fmt.Printf("child: %d\n", i)
}

// main: begin
// main: middle
// main: end
// child: 3
// child: 2
// child: 1
// child: 0

需要记住的一件事是,如果你需要在一个循环中调用defer ,你必须将相关功能移到一个额外的函数中,包括defer 语句本身。如果你不这样做,你会造成资源 "泄漏",请看下面的区别:

// This is wrong!

func main() {
	fmt.Println("main: begin")

	for i := 1; i < 4; i++ {
		defer child(i) // Resource leak.
	}

	fmt.Println("main: end")
}

func child(i int) {
	fmt.Printf("child: %d\n", i)
}

// main: begin
// total: 8
// main: end
// child: 3
// child: 2
// child: 1

在上面的例子中,推迟的操作将不得不等待循环的完成。因此,它将导致资源 "泄漏 "的原因。然而,下面的例子没有这样的问题,因为,推迟的操作将在每个迭代中立即运行。请注意输出:

// This correct!

func main() {
	total := 1

	fmt.Println("main: begin")

	for i := 1; i < 4; i++ {
		total = total * 2
		middleman(i)
	}

	fmt.Printf("total: %d\n", total)
	fmt.Println("main: end")
}

func middleman(i int) {
	defer child(i)
}

func child(i int) {
	fmt.Printf("child: %d\n", i)
}

// main: begin
// child: 1
// child: 2
// child: 3
// total: 8
// main: end