在延迟函数内部恐慌,特别是当它已经发生恐慌时,这样做是否可以?

huangapple go评论83阅读模式
英文:

Is it okay to panic inside defer function, especially when it's already panicking?

问题

func sub(){
defer func (){
panic(2)
}()
panic(1)
}

func main(){
defer func(){
x:=recover()
println(x.(int));
}()
sub()
}

我尝试了这段代码,似乎第一个 panic panic(1) 被第二个 panic panic(2) "覆盖"了。

但是这样做可以吗?或者在 defer 函数中调用可能会引发 panic 的 Golang 函数可以吗?

(在 C++ 中,如果在析构函数中抛出异常,几乎是不可接受的。如果堆栈已经在展开,它会终止程序。我想知道在 Golang 中以类似的方式引发 panic 是否会有问题。)

英文:
func sub(){
	defer func (){
		panic(2)
	}()
	panic(1)
}

func main(){
	defer func(){
		x:=recover()
		println(x.(int));
	}()
	sub()
}

I tried this code and it seems the first panic panic(1) is simply "overwritten" by the second panic panic(2).

But is it okay to do that? Or call a Golang function that might panic inside defer function?

(In C++ it's almost never acceptable to throw exception out of a destructor. It terminates program if stack is already unwinding. I wonder if panicking in a similar manner could be bad in Golang.)

答案1

得分: 12

是的,没问题。从延迟函数中引发的恐慌并不是一个新的、特殊的状态,它只是意味着恐慌序列不会停止。

你的示例代码也证明了这一点,甚至从延迟函数中调用的panic()也可以被一个"上层"调用的recover()停止。

规范:处理恐慌:

假设函数G延迟调用了一个调用recover的函数D,并且在执行G的相同goroutine中的一个函数中发生了恐慌。当延迟函数的运行到达D时,D调用recover的返回值将是传递给panic调用的值。**如果D正常返回,而不是启动新的恐慌,那么恐慌序列将停止。**在这种情况下,调用G和调用panic之间调用的函数的状态将被丢弃,正常执行将恢复。

需要注意的一点是,即使在延迟函数中调用了panic(),所有其他的延迟函数仍然会运行。而且,一个没有recover()的延迟函数中的panic()会将现有的恐慌"包装"起来,而不是"覆盖"它(尽管recover()调用只会将值返回给最后一个panic()调用)。

看看这个例子:

func main() {
	defer func() {
		fmt.Println("Checkpoint 1")
		panic(1)
	}()
	defer func() {
		fmt.Println("Checkpoint 2")
		panic(2)
	}()
	panic(999)
}

输出结果(在Go Playground上尝试):

Checkpoint 2
Checkpoint 1
panic: 999
	panic: 2
	panic: 1

goroutine 1 [running]:
panic(0xfed00, 0x1040e140)
	/usr/local/go/src/runtime/panic.go:500 +0x720
main.main.func1()
	/tmp/sandbox284410661/main.go:8 +0x120
panic(0xfed00, 0x1040e0fc)
	/usr/local/go/src/runtime/panic.go:458 +0x8a0
main.main.func2()
	/tmp/sandbox284410661/main.go:12 +0x120
panic(0xfed00, 0x1040e0f8)
	/usr/local/go/src/runtime/panic.go:458 +0x8a0
main.main()
	/tmp/sandbox284410661/main.go:14 +0xc0

尽管所有的延迟函数都调用了panic(),但所有的延迟函数都会被执行,并且最终打印的恐慌序列包含了所有panic()调用传递的值。

如果在延迟函数中调用recover(),你还可以在最终的打印输出中得到这个"恢复"状态或信息:

defer func() {
	recover()
	fmt.Println("Checkpoint 1")
	panic(1)
}()
defer func() {
	recover()
	fmt.Println("Checkpoint 2")
	panic(2)
}()

输出结果(在Go Playground上尝试):

Checkpoint 2
Checkpoint 1
panic: 999 [recovered]
	panic: 2 [recovered]
	panic: 1
...
英文:

Yes, it's okay. Panicking from a deferred function is not really a new, special state, it just means that the panicking sequence will not stop.

Your example code also proves that it's okay, and even a panic() called from a deferred function can be stopped by an "upper" level call to recover().

Spec: Handling panics:

> Suppose a function G defers a function D that calls recover and a panic occurs in a function on the same goroutine in which G is executing. When the running of deferred functions reaches D, the return value of D's call to recover will be the value passed to the call of panic. If D returns normally, without starting a new panic, the panicking sequence stops. In that case, the state of functions called between G and the call to panic is discarded, and normal execution resumes.

One thing to note here is that even if you call panic() in a deferred function, still all the other deferred functions will run. Also a panic() without recover() from a deferred function will rather "wrap" the existing panic and not "overwrite" it (although it's true that a recover() call will only give you back the value passed to the last panic() call).

See this example:

func main() {
	defer func() {
		fmt.Println("Checkpoint 1")
		panic(1)
	}()
	defer func() {
		fmt.Println("Checkpoint 2")
		panic(2)
	}()
	panic(999)
}

Output (try it on the Go Playground):

Checkpoint 2
Checkpoint 1
panic: 999
	panic: 2
	panic: 1

goroutine 1 [running]:
panic(0xfed00, 0x1040e140)
	/usr/local/go/src/runtime/panic.go:500 +0x720
main.main.func1()
	/tmp/sandbox284410661/main.go:8 +0x120
panic(0xfed00, 0x1040e0fc)
	/usr/local/go/src/runtime/panic.go:458 +0x8a0
main.main.func2()
	/tmp/sandbox284410661/main.go:12 +0x120
panic(0xfed00, 0x1040e0f8)
	/usr/local/go/src/runtime/panic.go:458 +0x8a0
main.main()
	/tmp/sandbox284410661/main.go:14 +0xc0

Even though all deferred functions call panic(), all deferred functions get executed, and the final panic sequence printed contains values passed to all panic() calls.

If you call recover() in the deferred functions, you also get this "recovered" state or info in the final printout:

defer func() {
	recover()
	fmt.Println("Checkpoint 1")
	panic(1)
}()
defer func() {
	recover()
	fmt.Println("Checkpoint 2")
	panic(2)
}()

Output (try it on the Go Playground):

Checkpoint 2
Checkpoint 1
panic: 999 [recovered]
	panic: 2 [recovered]
	panic: 1
...

huangapple
  • 本文由 发表于 2016年12月14日 17:49:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/41139447.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定