Go语言中的defer执行时机以及panic如何正常恢复

   1. defer简单介绍

         defer关键字后的函数或则和方法想要执行必须先注册,return或者panic之后的defer是不能注册的,这是大前提.

Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步.而defer语句的执行的时机就在返回值赋值操作后,RET指令前.

 1.1 defer函数执行时机

func calc(index string, a, b int) int {
	ret := a + b
	fmt.Println(index, a, b, ret)
	return ret
}

func main() {
	x := 1
	y := 2
	defer calc("AA", x, calc("A", x, y))
	x = 10
	defer calc("BB", x, calc("B", x, y))
	y = 20
}

A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4
type Slice []int

func NewSlice() Slice {
	return make(Slice, 0)
}
func (s *Slice) Add(elem int) *Slice {
	*s = append(*s, elem)
	fmt.Print(elem)
	return s
}
func main() {
	s := NewSlice()
	defer s.Add(1).Add(2)
	s.Add(3)
}

//132

        defer后面的函数如果携带有参数(包括接收者),会优先计算参数,并将结果存储在栈中,到真正执行defer()的时候取出.并且defer在执行时,是先进后出的.即靠后的defer会先执行.

1.2 defer中函数的具名返回值和匿名返回值的区别

defer注册要延迟执行的函数时该函数所有的参数都需要确定其值

func main() {
	fmt.Println("f1 result: ", f1())
	/*
		f12 1001
		f11 1002
		f1 1000
	*/
	fmt.Println("f2 result: ", f2())
	/*
		f22 1001
		f21 1002
		f2 1002
	*/
}
func f1() int {
	var i int
	defer func() {
		i++
		fmt.Println("f11: ", i)
	}()

	defer func() {
		i++
		fmt.Println("f12: ", i)
	}()

	i = 1000
	return i
}
func f2() (i int) {
	defer func() {
		i++
		fmt.Println("f21: ", i)
	}()

	defer func() {
		i++
		fmt.Println("f22: ", i)
	}()

	i = 1000
	return i
}

原因就是return会将返回值先保存起来,对于无名返回值来说, 保存在一个临时对象中,defer是看不到这个临时对象的; 而对于有名返回值来说,就保存在已命名的变量中。

1.3 defer注册函数时机

func f(n int) (r int) {
	defer func() {
		r += n // 9
		recover()
	}()
	var f1 func()
	f1 = func() {
		fmt.Println("f1被调用了")
		r += 2 // //r = 6
	}
	defer f1()
	return n + 1 // r = 4
}

func main() {
	fmt.Println(f(3))
}

//9
func f(n int) (r int) {
	defer func() {
		r += n // 7
		if err := recover(); err != nil {
			fmt.Println("异常")
		}
	}()
	var f1 func()
	defer f1()
	f1 = func() {
		fmt.Println("f1被调用了")
		r += 2 
	}
	return n + 1 // r = 4
}

func main() {
	fmt.Println(f(3))
}

// 7

第二个情况下,defer在注册f1时,由于f1()未定义,发生panic,第二个defer中的recover执行.

2.Go协程中的panic问题

在Go语言中,存在多个协程的情况下,单个协程触发panic会导致其它所有协程挂掉,每个协程只能捕获到自己的panic,不能捕获其它协程.

func main() {
	// 协程 A
	go func() {
		for {
			fmt.Println("协程 A")
		}
	}()

	// 协程 B
	go func() {
		time.Sleep(1 * time.Microsecond) // 确保 协程 A 先运行起来
		panic("协程 B panic")
	}()
	time.Sleep(10 * time.Second) // 充分等待协程 B 触发 panic 完成和协程 A 执行完毕
	fmt.Println("main end")
}

协程 A
协程 A
协程 A
协程 A
panic: 协程 B panic

goroutine 20 [running]:
main.main.func2()
        D:/softwore/GoRoute/GoAlgorithm/leecode/main.go:19 +0x2b
created by main.main in goroutine 1
        D:/softwore/GoRoute/GoAlgorithm/leecode/main.go:17 +0x26

从结果来看,B触发panic之后,不仅协程A停止了,而且主协程也挂掉了.

func main() {
	// 协程 A
	go func() {
		panic("协程A panic")
	}()
	defer func() {
		if err := recover(); err != nil {
			fmt.Printf("panic err is ", err)
		}
	}()
	time.Sleep(10 * time.Second) // 充分等待协程 B 触发 panic 完成和协程 A 执行完毕
	fmt.Println("main end")
}

panic: 协程A panic

goroutine 6 [running]:
main.main.func1()
        D:/softwore/GoRoute/GoAlgorithm/leecode/main.go:11 +0x25
created by main.main in goroutine 1
        D:/softwore/GoRoute/GoAlgorithm/leecode/main.go:10 +0x2a

从结果来看,主协程无法recover到协程A的panic.哪个协程发生panic,就需要在哪个协程(发生panic之前)自身中recover.并且recover只能放在 defer后面的函数中执行.

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值