go:程序怎样recover

这是 A Journey With Go翻译的第二篇文章,大家感兴趣可以去看看原文。我是带着学习的目的去翻译,既能学习英文,又能学习技术,一举二得。

所以翻译的不好是正常的,有疑问可以交流。

正文

在go中如果没有恰当的处理error,会造成panic,比如非法的内存操作。 如果这个错误是预料之外的,也没有其它的处理方法,也可以由开发者主动panic。

了解recover或终止是如何处理的,有助于理解一个会发生panic的程序带来的后果

多层函数调用

关于panic和recover的经典的例子有被记录到文档里,收录在go的博客中“Defer, Panic, and Recover.” 让我们关注另一个例子,其中panic涉及多个defer函数的帧时

package main

func main() {
	defer println("defer 1")
	level1()
}
func level1() {
	defer println("defer 3")
	defer func() {
		if err := recover(); err != nil {
			println("revoring in progress")
		}
	}()
	defer println("defer 2")
	level2()
}
func level2() {
	defer println("defer func 4")
	panic("foo")
}

这段代码的输出为:

defer func 4

defer 2

revoring in progress

defer 3

defer 1

这段代码有三个函数组成,他们是链式调用。一旦代码运行到leve2函数中,将会触发panic,然后执行defer

这段红色框中的代码不会recover这个panic,然后会返回到父调用者也就是level1中,依次执行defer函数

值得一提的是,defer的执行顺序是LIFO,后进先出,类似于stack。更多关于defer的内容可以看另一篇文章Go: How Does defer Statement Work?

由于在level1中将会recover这个panic,go需要一个方法追踪记录它并恢复程序的执行。

为此,每个goroutine有一个特殊的属性指向一个代表该panic的对象。

当一个panic发生时,这个对象将会被创建在执行defer之前。然后recover这个panic的函数实际上仅仅返回这个panic对象的信息,同时标示被恢复了

一旦panic被认为已恢复,go将会继续当前的工作,然而 由于运行时在多个defer函数中,它不知道从哪继续执行。鉴于这个原因,

Go保存当前程序计数器和当前帧的堆栈指针,以便panic发生后恢复该函数

我们可以使用objdump,检查当前程序计数器

(e.g. objdump -D my-binary | grep 105acef):

这个指令指向untime.deferreturn,被编译器插入到每个有defer的函数的最后。在上面的例子中,直到recover前大部分的已经运行了,

因此剩下的将会运行在return到调用者之前。

WaitGroup

理解这个工作流向我们展示了defer功能的重要性,以及它是如何发挥作用的

举个例子,当使用一组goroutine时,

在延迟函数中使用defer对WaitGroup对象的调用可以防止死锁

package main

import "sync"

func main() {
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer func() {
			if err := recover(); err != nil {
				println(err.(string))
			}
		}()
		p()
		wg.Done()
		
		
	}()
	wg.Wait()
}
func p() {
	panic("foo")
}

这个程序会导致死锁,因为wg.Done从来不会被调用。使用 defer可以解决这个问题。

package main

import "sync"

func main() {
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer func() {
			if err := recover(); err != nil {
				println(err.(string))
			}
		}()
		defer wg.Done()
        p()
		
		
	}()
	wg.Wait()
}
func p() {
	panic("foo")
}

Goexit

值得注意的是,这个函数runtime.Goexit使用完全相同的工作流。它实际上创建了一个带有特殊标志的panic对象,以区别于真正的panic。

此标志允许运行时跳过recover并正确退出,而不是停止程序的执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值