持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
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无法恢复,所以一般写代码的时候要多思考、多测试