go defer运行机制

概念

Go 语言的 defer 会在当前函数返回前执行传入的函数,它会经常被用于关闭文件描述符、关闭数据库连接以及解锁资源。

现象

  1. defer 先进后出

    package main
    
    import "fmt"
    
    func main() {
    	for i := 0; i < 5; i++ {
    		defer fmt.Println(i)
    	}
    }
    
    go run main.go   
    4
    3
    2
    1
    0
    
  2. defer 传入的函数不是在退出代码块的作用域时执行的,它只会在当前函数和方法返回之前被调用

    package main
    
    import "fmt"
    
    func main() {
    	func() {
    		defer fmt.Println("匿名函数结束")
    		fmt.Println("匿名函数。。。")
    	}()
    	{
    		defer fmt.Println("代码块结束")
    		fmt.Println("代码块。。。")
    	}
    	fmt.Println("正常")
    }
    
    go run main.go
    匿名函数。。。
    匿名函数结束
    代码块。。。
    正常
    代码块结束
    
  3. 参数的计算

    对于 defer2,因为 i 是以参数的形式传递进去的,所以 defer 会立刻拷贝 i 参数,此时的 i=5

    对于 defer1,因为函数执行结束时 i=7,defer 中使用的是外部的 i,所以 defer 中也是 7

    package main
    
    import "fmt"
    
    func main() {
    	defer1()
    	defer2()
    }
    
    func defer1() {
    	i := 5
    
    	defer func() {
    		fmt.Println("defer: ", i)
    	}()
    
    	i = 7
    	fmt.Println("正常:", i)
    }
    
    func defer2() {
    	i := 5
    
    	defer func(i int) {
    		fmt.Println("defer: ", i)
    	}(i)
    
    	i = 7
    	fmt.Println("正常:", i)
    }
    
    go run main.go
    正常: 7
    defer:  7
    正常: 7
    defer:  5
    

    同样的道理,结构体也是如此,当为引用时复制的只是结构体的地址,指向的还是同一块内存,当结构体改变时 defer 中打印的也是改变之后的值

    package main
    
    import "fmt"
    
    func main() {
    	defer3()
    	defer4()
    	defer5()
    }
    
    type Student struct {
    	Name string
    }
    
    func defer3() {
    	s := Student{"liu"}
    
    	defer func() {
    		fmt.Println("defer: ", s)
    	}()
    
    	s.Name = "kang"
    	fmt.Println("正常:", s)
    }
    
    func defer4() {
    	s := Student{"liu"}
    
    	defer func(s Student) {
    		fmt.Println("defer: ", s)
    	}(s)
    
    	s.Name = "kang"
    	fmt.Println("正常:", s)
    }
    
    func defer5() {
    	s := &Student{"liu"}
    
    	defer func(s *Student) {
    		fmt.Println("defer: ", s)
    	}(s)
    
    	s.Name = "kang"
    	fmt.Println("正常:", s)
    }
    
    go run main.go
    正常: {kang}
    defer:  {kang}
    正常: {kang}
    defer:  {liu}
    正常: &{kang}
    defer:  &{kang}
    

原理