1、什么是延迟语句
defer是go语言提供的一种注册延迟调用的机制,通常用来:打开/关闭连接,打开/关闭锁,打开/关闭文件等
defer + 、语句、 复制代码
2、defer语句的执行顺序
- 同一个函数内的defer是“后进先出”的顺序,defer语句会被压入一个栈,当外层函数结束的时候,汇之星这个栈中的defer语句
func test(){ fmt.Println(1) fmt.Println(2) } 复制代码
- 执行的结果是2,1
3、defer语句的变量必须是确定值才会被压入栈中
- defer执行的时候,函数或者语句中的变量的值必须是确定的才会被压入栈中
func add(x, y int) int { c := x + y fmt.Println(c) return c } func testI3() { a := 1 b := 2 defer fmt.Println("a:", a) defer add(a, add(a, b)) a = 3 defer fmt.Println("a:", a) } 复制代码
- 这段代码得到的结果是3、 a: 3、 4、 a: 1
- 这是执行第二个defer时候,会先确定add中另一个参数的值,所以会输出一个3,然后编程defer add(a,3)。
- 这时候第二个defer被压入栈中,然后以后进先出的顺序执行三个defer
4、defer函数变量引用
- defer函数变量引用有两种方式:参数和闭包
- 参数分为值类型和引用类型
- 值类型会进行一个拷贝
- 引用类型会在函数结束时所确定的执行
- 如果是闭包,会根据defer真正调用时的上下文环境确定
//闭包 for i := 0; i < 3; i++ { defer func() { fmt.Println(i) }() } //引用类型 var a = new(int) *a = 1 defer func() { fmt.Println(*a) }() *a = 2 复制代码
- 最终的结果是3 3 3 、 2
5、defer的执行时机,拆解defer
- 在go语言中return并不是一个原子操作,会生成两个指令
- 1、返回值 == xxx
- 2、真正结束
- 如果有defer语句,defer会在1和2之间执行
func main(){ c1 := add(1,2) c2 := sub(1,2) fmt.Println(c1,c2) } func add(x, y int)(c int){ defer func (){ c = 10 }() return x + y } func sub(x,y int)(c int){ c = x - y defer func(){ c-- }() return } 复制代码
- 结果是10 2
6、defer配合恢复语句
- panic会停掉当前正在执行的程序,而不只是当前线程
- 而遇到panic后,程序会先执行当前线程defer列表中的defer语句,这样我们就可以配合一些语句恢复线程-- recover
- defer -- recover 捕捉panic的原则
- panic必须在defer函数中调用,而不能多层嵌套,也不能平级
defer func(){ recover() }() panic("now is err!") 复制代码
7、return和painc之后的defer不会被执行
- return和panic之后的defer不会被注册,下面可以自己测试一下
//return之后 defer func(){ fmt.Println("a") }() if true { fmt.Println("b") return } defer func(){ fmt.Println("c") }() 复制代码
//panic之后 panic("now is err") defer func(){ recover() }() 复制代码
- 事实上,return和panic之后所有语句都不会被注册
8、为什么无法从其他协程中恢复当前协程的panic
- 每个goroutine都被设计成一个独立的单元,有自己单独的执行享的数据,如果要通信只能使用channel或者加读写锁。这也就意味着没有返回值他的数据与goroutine交互。
- 当然也可以通过channel设计一个通信机制,但是这样不够灵活。而且有的panic无法恢复,所以一般写代码的时候要多思考、多测试