开场白
注:上面的链接的文章非常棒,需要大家看完链接文章后,才能往下看
第一个例子
假如你看明白了上述的文章,那我举两个例子:
package main
import (
"fmt"
)
func main() {
fmt.Println("ok1")
defer func() { // defer1
bb := recover()
fmt.Println("bb =", bb)
}()
defer func() { // defer2
fmt.Println("ok2")
panic("panic again")
}()
panic("panic1")
}
ok1
ok2
bb = panic again
这个例子说明了啥?为什么两个panic,recover了一次,程序却能正常运行,那我们就从源码上来分析。
我们暂且命名panic1和panic2,以及defer1和defer2
通过上述文章的了解,大家已经知道当代码显示调用panic的时候,真正实现这个功能的函数是
func gopanic(e interface{}) {
......
var p _panic
p.arg = e
p.link = gp._panic
gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
for {
d := gp._defer
if d == nil {
break
}
// If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic),
// take defer off list. The earlier panic or Goexit will not continue running.
if d.started {
if d._panic != nil {
d._panic.aborted = true
}
d._panic = nil
d.fn = nil
gp._defer = d.link
freedefer(d)
continue
}
// Mark defer as started, but keep on list, so that traceback
// can find and update the defer's argument frame if stack growth
// or a garbage collection happens before reflectcall starts executing d.fn.
d.started = true
// Record the panic that is running the defer.
// If there is a new panic during the deferred call, that panic
// will find d in the list and will mark d._panic (this panic) aborted.
d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
p.argp = unsafe.Pointer(getargp(0))
reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
p.argp = nil
// reflectcall did not panic. Remove d.
if gp._defer != d {
throw("bad defer entry in panic")
}
d._panic = nil
d.fn = nil
gp._defer = d.link
// trigger shrinkage to test stack copy. See stack_test.go:TestStackPanic
//GC()
pc := d.pc
sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
freedefer(d)
if p.recovered {
gp._panic = p.link
// Aborted panics are marked but remain on the g.panic list.
// Remove them from the list.
for gp._panic != nil && gp._panic.aborted {
gp._panic = gp._panic.link
}
if gp._panic == nil { // must be done with signal
gp.sig = 0
}
// Pass information about recovering frame to recovery.
gp.sigcode0 = uintptr(sp)
gp.sigcode1 = pc
mcall(recovery)
throw("recovery failed") // mcall should not return
}
}
// ran out of deferred calls - old-school panic now
// Because it is unsafe to call arbitrary user code after freezing
// the world, we call preprintpanics to invoke all necessary Error
// and String methods to prepare the panic strings before startpanic.
preprintpanics(gp._panic)
startpanic()
printpanics(gp._panic)
dopanic(0) // should not return
*(*int)(nil) = 0 // not reached
}
15行的d.started是一个很容易让人忽略的属性,
当panic1触发首先调用的是defer2时发生赋值:
defer2.started = true,
defer2.panic = panic1,
此时调用reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))这里需要注意,将一去不复返了,为什么呢?往下分析
defer func() { // defer2
fmt.Println("ok2")
panic("panic again")
}()
此时reflectall调用defer2里面的代码会触发再次触发gopanic,从gp._defer链表的表头defer2开始,因为defer2.started=true已经被panic1调用时赋值,所以此时进入如下逻辑
// If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic),
// take defer off list. The earlier panic or Goexit will not continue running.
if d.started {
if d._panic != nil {
d._panic.aborted = true
}
d._panic = nil
d.fn = nil
gp._defer = d.link
freedefer(d)
continue
}
把panic1.aborted设置为true,然后continue重新获取链表的defer1,并且此时调用defer1的时候发现了recover方法, 在这里解决的是panic2
if p.recovered {
atomic.Xadd(&runningPanicDefers, -1)
gp._panic = p.link
// Aborted panics are marked but remain on the g.panic list.
// Remove them from the list.
for gp._panic != nil && gp._panic.aborted {
gp._panic = gp._panic.link
}
if gp._panic == nil { // must be done with signal
gp.sig = 0
}
// Pass information about recovering frame to recovery.
gp.sigcode0 = uintptr(sp)
gp.sigcode1 = pc
mcall(recovery)
throw("recovery failed") // mcall should not return
其中需要注意的是循环处理
for gp._panic != nil && gp._panic.aborted {
gp._panic = gp._panic.link
}
因为panic1.aborted=true, 所以将其从panic链表上剔除掉,这个时候gp._panic链表此时为空了,所以也不会再异常了。
总结:如果两个或者多个panic所关联的协程的defer属于同一个,即可一个recover成功。
第二个例子
package main
import (
"fmt"
)
func main() {
fmt.Println("ok1")
defer func() { // defer1
fmt.Println("ok2")
defer func() { //defer2
fmt.Println("ok5")
aa := recover()
fmt.Println("ok4", aa)
}()
panic("panic again")
}()
panic("panic1")
}
ok1
ok2
ok5
ok4 panic again
panic: panic1
goroutine 1 [running]:
main.main()
/tmp/test.go:35 +0xbd
这个例子捕获了panic2,未捕获panic1,从而程序崩溃。
我们来分析下
当panic1触发首先调用的是gb._defer链表的表头defer1,此时赋值:
defer1.started = true,
defer1.panic = panic1,
然后调用defer里面的延迟函数reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz)),
在执行defer内延迟函数时新增了一个defer2,此刻defer2会挂再gb._defer后,同时又出发了gopanic,但是注意的是,此时可gopanic对应的进入的逻辑是从表头取出的是defer2(这里是与第一个例子不同的关键所在)
defer2.started=true,
defer2.panic=panic2,
然后在defer2中发现了recover并恢复过程中,panic2.recovery=true,得到了恢复,但是panic链表中的panic1并未得到解决,遍历gb._defer链表后,没有recovery函数,所以最终panic发生。
深入理解Go的panic和recover
1209

被折叠的 条评论
为什么被折叠?



