- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章详解go中panic源码解读由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
。
前言 。
本文是在go version go1.13.15 darwin/amd64上进行的 。
panic的作用 。
panic
能够改变程序的控制流,调用panic
后会立刻停止执行当前函数的剩余代码,并在当前Goroutine
中递归执行调用方的defer
;recover
可以中止panic
造成的程序崩溃。它是一个只能在defer
中发挥作用的函数,在其他作用域中调用不会发挥作用;举个栗子 。
1
2
3
4
5
6
7
8
9
10
11
12
|
package main
import
"fmt"
func main() {
fmt.Println(
1
)
func() {
fmt.Println(
2
)
panic(
"3"
)
}()
fmt.Println(
4
)
}
|
输出 。
1 2 panic: 3 。
goroutine 1 [running]: main.main.func1(...) /Users/yj/Go/src/Go-POINT/panic/main.go:9 main.main() /Users/yj/Go/src/Go-POINT/panic/main.go:10 +0xee 。
panic后会立刻停止执行当前函数的剩余代码,所以4没有打印出来 。
对于recover 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package main
import
(
"fmt"
"time"
)
func main() {
fmt.Println(
1
)
defer func() {
if
err :
=
recover(); err !
=
nil {
fmt.Println(err)
}
}()
go func() {
fmt.Println(
2
)
panic(
"3"
)
}()
time.Sleep(time.Second)
fmt.Println(
4
)
}
|
上面的栗子,因为recover和panic不在同一个goroutine中,所以不会捕获到 。
嵌套的demo 。
1
2
3
4
5
6
7
8
9
10
11
|
func main() {
defer fmt.Println(
"in main"
)
defer func() {
defer func() {
panic(
"3 panic again and again"
)
}()
panic(
"2 panic again"
)
}()
panic(
"1 panic once"
)
}
|
输出 。
in main panic: 1 panic once panic: 2 panic again panic: 3 panic again and again 。
goroutine 1 [running]: ... 。
多次调用panic也不会影响defer函数的正常执行,所以使用defer进行收尾工作一般来说都是安全的.
需要注意的是,你应该尽可能地使用error,而不是使用panic和recover。只有当程序不能继续运行的时候,才应该使用panic和recover机制.
panic有两个合理的用例.
1、发生了一个不能恢复的错误,此时程序不能继续运行。 一个例子就是 web 服务器无法绑定所要求的端口。在这种情况下,就应该使用 panic,因为如果不能绑定端口,啥也做不了.
2、发生了一个编程上的错误。 假如我们有一个接收指针参数的方法,而其他人使用 nil 作为参数调用了它。在这种情况下,我们可以使用panic,因为这是一个编程错误:用 nil 参数调用了一个只能接收合法指针的方法.
在一般情况下,我们不应通过调用panic函数来报告普通的错误,而应该只把它作为报告致命错误的一种方式。当某些不应该发生的场景发生时,我们就应该调用panic.
总结下panic的使用场景
1、空指针引用 。
2、下标越界 。
3、除数为0 。
4、不应该出现的分支,比如default 。
5、输入不应该引起函数错误 。
。
先来看下_panic的结构 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/
/
_panic 保存了一个活跃的 panic
/
/
/
/
这个标记了 go:notinheap 因为 _panic 的值必须位于栈上
/
/
/
/
argp 和 link 字段为栈指针,但在栈增长时不需要特殊处理:因为他们是指针类型且
/
/
_panic 值只位于栈上,正常的栈指针调整会处理他们。
/
/
/
/
go:notinheap
type
_panic struct {
argp unsafe.Pointer
/
/
panic 期间 defer 调用参数的指针; 无法移动
-
liblink 已知
arg interface{}
/
/
panic的参数
link
*
_panic
/
/
link 链接到更早的 panic
recovered
bool
/
/
panic是否结束
aborted
bool
/
/
panic是否被忽略
}
|
link指向了保存在goroutine链表中先前的panic链表 。
编译器会将panic装换成gopanic,来看下执行的流程:
1、创建新的runtime._panic并添加到所在Goroutine的_panic链表的最前面; 。
2、在循环中不断从当前Goroutine 的_defer中链表获取runtime._defer并调用runtime.reflectcall运行延迟调用函数; 。
3、调用runtime.fatalpanic中止整个程序; 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
/
/
预先声明的函数 panic 的实现
func gopanic(e interface{}) {
gp :
=
getg()
/
/
判断在系统栈上还是在用户栈上
/
/
如果执行在系统或信号栈时,getg() 会返回当前 m 的 g0 或 gsignal
/
/
因此可以通过 gp.m.curg
=
=
gp 来判断所在栈
/
/
系统栈上的 panic 无法恢复
if
gp.m.curg !
=
gp {
print
(
"panic: "
)
printany(e)
print
(
"\n"
)
throw(
"panic on system stack"
)
}
/
/
如果正在进行 malloc 时发生 panic 也无法恢复
if
gp.m.mallocing !
=
0
{
print
(
"panic: "
)
printany(e)
print
(
"\n"
)
throw(
"panic during malloc"
)
}
/
/
在禁止抢占时发生 panic 也无法恢复
if
gp.m.preemptoff !
=
"" {
print
(
"panic: "
)
printany(e)
print
(
"\n"
)
print
(
"preempt off reason: "
)
print
(gp.m.preemptoff)
print
(
"\n"
)
throw(
"panic during preemptoff"
)
}
/
/
在 g 锁在 m 上时发生 panic 也无法恢复
if
gp.m.locks !
=
0
{
print
(
"panic: "
)
printany(e)
print
(
"\n"
)
throw(
"panic holding locks"
)
}
/
/
下面是可以恢复的
var p _panic
p.arg
=
e
/
/
panic 保存了对应的消息,并指向了保存在 goroutine 链表中先前的 panic 链表
p.link
=
gp._panic
gp._panic
=
(
*
_panic)(noescape(unsafe.Pointer(&p)))
atomic.Xadd(&runningPanicDefers,
1
)
for
{
/
/
开始逐个取当前 goroutine 的 defer 调用
d :
=
gp._defer
/
/
没有defer,退出循环
if
d
=
=
nil {
break
}
/
/
如果 defer 是由早期的 panic 或 Goexit 开始的(并且,因为我们回到这里,这引发了新的 panic),
/
/
则将 defer 带离链表。更早的 panic 或 Goexit 将无法继续运行。
if
d.started {
if
d._panic !
=
nil {
d._panic.aborted
=
true
}
d._panic
=
nil
d.fn
=
nil
gp._defer
=
d.link
freedefer(d)
continue
}
/
/
将deferred标记为started
/
/
如果栈增长或者垃圾回收在 reflectcall 开始执行 d.fn 前发生
/
/
标记 defer 已经开始执行,但仍将其保存在列表中,从而 traceback 可以找到并更新这个 defer 的参数帧
/
/
标记defer是否已经执行
d.started
=
true
/
/
记录正在运行的延迟的panic。
/
/
如果在延迟调用期间有新的panic,那么这个panic
/
/
将在列表中找到d,并将标记d._panic(此panic)中止。
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没有panic。删除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 {
atomic.Xadd(&runningPanicDefers,
-
1
)
gp._panic
=
p.link
/
/
忽略的 panic 会被标记,但仍然保留在 g.panic 列表中
/
/
这里将它们移出列表
for
gp._panic !
=
nil && gp._panic.aborted {
gp._panic
=
gp._panic.link
}
if
gp._panic
=
=
nil {
/
/
必须由 signal 完成
gp.sig
=
0
}
/
/
传递关于恢复帧的信息
gp.sigcode0
=
uintptr(sp)
gp.sigcode1
=
pc
/
/
调用 recover,并重新进入调度循环,不再返回
mcall(recovery)
/
/
如果无法重新进入调度循环,则无法恢复错误
throw(
"recovery failed"
)
/
/
mcall should
not
return
}
}
/
/
消耗完所有的 defer 调用,保守地进行 panic
/
/
因为在冻结之后调用任意用户代码是不安全的,所以我们调用 preprintpanics 来调用
/
/
所有必要的 Error 和 String 方法来在 startpanic 之前准备 panic 字符串。
preprintpanics(gp._panic)
fatalpanic(gp._panic)
/
/
不应该返回
*
(
*
int
)(nil)
=
0
/
/
无法触及
}
/
/
reflectcall 使用 arg 指向的 n 个参数字节的副本调用 fn。
/
/
fn 返回后,reflectcall 在返回之前将 n
-
retoffset 结果字节复制回 arg
+
retoffset。
/
/
如果重新复制结果字节,则调用者应将参数帧类型作为 argtype 传递,以便该调用可以在复制期间执行适当的写障碍。
/
/
reflect 包传递帧类型。在 runtime 包中,只有一个调用将结果复制回来,即 cgocallbackg1,
/
/
并且它不传递帧类型,这意味着没有调用写障碍。参见该调用的页面了解相关理由。
/
/
/
/
包 reflect 通过 linkname 访问此符号
func reflectcall(argtype
*
_type, fn, arg unsafe.Pointer, argsize uint32, retoffset uint32)
|
梳理下流程 。
1、在处理panic期间,会先判断当前panic的类型,确定panic是否可恢复,
2、可恢复的panic,panic的link指向goroutine链表中先前的panic链表; 。
3、循环逐个获取当前goroutine的defer调用; 。
recover
了一个panic
时,就会返回1,当runtime.deferproc
函数的返回值是1时,编译器生成的代码会直接跳转到调用方函数返回之前并执行runtime.deferreturn
,跳转到runtime.deferturn
函数之后,程序就已经从panic
恢复了正常的逻辑。而runtime.gorecover
函数也能从runtime._panic
结构中取出了调用panic
时传入的arg
参数并返回给调用方。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/
/
在发生 panic 后 defer 函数调用 recover 后展开栈。然后安排继续运行,
/
/
就像 defer 函数的调用方正常返回一样。
func recovery(gp
*
g) {
/
/
Info about defer passed
in
G struct.
sp :
=
gp.sigcode0
pc :
=
gp.sigcode1
/
/
d's arguments need to be
in
the stack.
if
sp !
=
0
&& (sp < gp.stack.lo || gp.stack.hi < sp) {
print
(
"recover: "
,
hex
(sp),
" not in ["
,
hex
(gp.stack.lo),
", "
,
hex
(gp.stack.hi),
"]\n"
)
throw(
"bad recovery"
)
}
/
/
使 deferproc 为此 d 返回
/
/
这时候返回
1
。调用函数将跳转到标准的返回尾声
gp.sched.sp
=
sp
gp.sched.pc
=
pc
gp.sched.lr
=
0
gp.sched.ret
=
1
gogo(&gp.sched)
}
|
在recovery函数中,利用g中的两个状态码回溯栈指针sp并恢复程序计数器pc到调度器中,并调用gogo重新调度g,将g恢复到调用recover函数的位置,goroutine继续执行,recovery在调度过程中会将函数的返回值设置为1。调用函数将跳转到标准的返回尾声.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func deferproc(siz int32, fn
*
funcval) {
/
/
arguments of fn follow fn
...
/
/
deferproc returns
0
normally.
/
/
a deferred func that stops a panic
/
/
makes the deferproc
return
1.
/
/
the code the compiler generates always
/
/
checks the
return
value
and
jumps to the
/
/
end of the function
if
deferproc returns !
=
0.
return0()
/
/
No code can go here
-
the C
return
register has
/
/
been
set
and
must
not
be clobbered.
}
|
当延迟函数中recover了一个panic时,就会返回1,当runtime.deferproc函数的返回值是1时,编译器生成的代码会直接跳转到调用方函数返回之前并执行runtime.deferreturn,跳转到runtime.deferturn函数之后,程序就已经从panic恢复了正常的逻辑。而runtime.gorecover函数也能从runtime._panic结构中取出了调用panic时传入的arg参数并返回给调用方.
编译器会将recover装换成gorecover 。
如果recover被正确执行了,也就是gorecover,那么recovered将被标记成true 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/
/
go
/
src
/
runtime
/
panic.go
/
/
执行预先声明的函数 recover。
/
/
不允许分段栈,因为它需要可靠地找到其调用者的栈段。
/
/
/
/
TODO(rsc): Once we commit to CopyStackAlways,
/
/
this doesn't need to be nosplit.
/
/
go:nosplit
func gorecover(argp uintptr) interface{} {
/
/
必须在 panic 期间作为 defer 调用的一部分在函数中运行。
/
/
必须从调用的最顶层函数( defer 语句中使用的函数)调用。
/
/
p.argp 是最顶层 defer 函数调用的参数指针。
/
/
比较调用方报告的 argp,如果匹配,则调用者可以恢复。
gp :
=
getg()
p :
=
gp._panic
if
p !
=
nil && !p.recovered && argp
=
=
uintptr(p.argp) {
/
/
标记recovered
p.recovered
=
true
return
p.arg
}
return
nil
}
|
在正常情况下,它会修改runtime._panic的recovered字段,runtime.gorecover函数中并不包含恢复程序的逻辑,程序的恢复是由runtime.gopanic函数负责.
gorecover将recovered标记为true,然后gopanic就可以通过mcall调用recovery并重新进入调度循环 。
runtime.fatalpanic实现了无法被恢复的程序崩溃,它在中止程序之前会通过runtime.printpanics打印出全部的panic消息以及调用时传入的参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
/
/
go
/
src
/
runtime
/
panic.go
/
/
fatalpanic 实现了不可恢复的 panic。类似于 fatalthrow,
/
/
如果 msgs !
=
nil,则 fatalpanic 仍然能够打印 panic 的消息
/
/
并在 main 在退出时候减少 runningPanicDeferss
/
/
/
/
go:nosplit
func fatalpanic(msgs
*
_panic) {
/
/
返回程序计数寄存器指针
pc :
=
getcallerpc()
/
/
返回堆栈指针
sp :
=
getcallersp()
/
/
返回当前G
gp :
=
getg()
var docrash
bool
/
/
切换到系统栈来避免栈增长,如果运行时状态较差则可能导致更糟糕的事情
systemstack(func() {
if
startpanic_m() && msgs !
=
nil {
/
/
有 panic 消息和 startpanic_m 则可以尝试打印它们
/
/
startpanic_m 设置 panic 会从阻止 main 的退出,
/
/
因此现在可以开始减少 runningPanicDefers 了
atomic.Xadd(&runningPanicDefers,
-
1
)
printpanics(msgs)
}
docrash
=
dopanic_m(gp, pc, sp)
})
if
docrash {
/
/
通过在上述 systemstack 调用之外崩溃,调试器在生成回溯时不会混淆。
/
/
函数崩溃标记为 nosplit 以避免堆栈增长。
crash()
}
/
/
从系统推出
systemstack(func() {
exit(
2
)
})
*
(
*
int
)(nil)
=
0
/
/
not
reached
}
/
/
打印出当前活动的panic
func printpanics(p
*
_panic) {
if
p.link !
=
nil {
printpanics(p.link)
print
(
"\t"
)
}
print
(
"panic: "
)
printany(p.arg)
if
p.recovered {
print
(
" [recovered]"
)
}
print
(
"\n"
)
}
|
。
引一段来自【panic 和recover】的总结 。
1、编译器会负责做转换关键字的工作; 。
1、将panic和recover分别转换成runtime.gopanic和runtime.gorecover; 。
2、将defer转换成runtime.deferproc函数; 。
3、在调用defer的函数末尾调用runtime.deferreturn函数; 。
2、在运行过程中遇到runtime.gopanic方法时,会从Goroutine的链表依次取出runtime._defer结构体并执行; 。
3、如果调用延迟执行函数时遇到了runtime.gorecover就会将_panic.recovered标记成true并返回panic的参数; 。
1、在这次调用结束之后,runtime.gopanic会从runtime._defer结构体中取出程序计数器pc和栈指针sp并调用runtime.recovery函数进行恢复程序; 。
2、runtime.recovery会根据传入的pc和sp跳转回runtime.deferproc; 。
3、编译器自动生成的代码会发现runtime.deferproc的返回值不为0,这时会跳回runtime.deferreturn并恢复到正常的执行流程; 。
4、如果没有遇到runtime.gorecover就会依次遍历所有的runtime._defer,并在最后调用runtime.fatalpanic中止程序、打印panic的参数并返回错误码2; 。
。
【panic 和 recover】https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-panic-recover/ 【恐慌与恢复内建函数】https://golang.design/under-the-hood/zh-cn/part1basic/ch03lang/panic/ 【Go语言panic/recover的实现】https://zhuanlan.zhihu.com/p/72779197 【panic and recover】https://eddycjy.gitbook.io/golang/di-6-ke-chang-yong-guan-jian-zi/panic-and-recover 【翻了源码,我把 panic 与 recover 给彻底搞明白了】https://jishuin.proginn.com/p/763bfbd4ed8c 。
到此这篇关于详解go中panic源码解读的文章就介绍到这了,更多相关go panic源码内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://www.cnblogs.com/ricklz/p/14692264.html 。
最后此篇关于详解go中panic源码解读的文章就讲到这里了,如果你想了解更多关于详解go中panic源码解读的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我需要将文本放在 中在一个 Div 中,在另一个 Div 中,在另一个 Div 中。所以这是它的样子: #document Change PIN
奇怪的事情发生了。 我有一个基本的 html 代码。 html,头部, body 。(因为我收到了一些反对票,这里是完整的代码) 这是我的CSS: html { backgroun
我正在尝试将 Assets 中的一组图像加载到 UICollectionview 中存在的 ImageView 中,但每当我运行应用程序时它都会显示错误。而且也没有显示图像。 我在ViewDidLoa
我需要根据带参数的 perl 脚本的输出更改一些环境变量。在 tcsh 中,我可以使用别名命令来评估 perl 脚本的输出。 tcsh: alias setsdk 'eval `/localhome/
我使用 Windows 身份验证创建了一个新的 Blazor(服务器端)应用程序,并使用 IIS Express 运行它。它将显示一条消息“Hello Domain\User!”来自右上方的以下 Ra
这是我的方法 void login(Event event);我想知道 Kotlin 中应该如何 最佳答案 在 Kotlin 中通配符运算符是 * 。它指示编译器它是未知的,但一旦知道,就不会有其他类
看下面的代码 for story in book if story.title.length < 140 - var story
我正在尝试用 C 语言学习字符串处理。我写了一个程序,它存储了一些音乐轨道,并帮助用户检查他/她想到的歌曲是否存在于存储的轨道中。这是通过要求用户输入一串字符来完成的。然后程序使用 strstr()
我正在学习 sscanf 并遇到如下格式字符串: sscanf("%[^:]:%[^*=]%*[*=]%n",a,b,&c); 我理解 %[^:] 部分意味着扫描直到遇到 ':' 并将其分配给 a。:
def char_check(x,y): if (str(x) in y or x.find(y) > -1) or (str(y) in x or y.find(x) > -1):
我有一种情况,我想将文本文件中的现有行包含到一个新 block 中。 line 1 line 2 line in block line 3 line 4 应该变成 line 1 line 2 line
我有一个新项目,我正在尝试设置 Django 调试工具栏。首先,我尝试了快速设置,它只涉及将 'debug_toolbar' 添加到我的已安装应用程序列表中。有了这个,当我转到我的根 URL 时,调试
在 Matlab 中,如果我有一个函数 f,例如签名是 f(a,b,c),我可以创建一个只有一个变量 b 的函数,它将使用固定的 a=a1 和 c=c1 调用 f: g = @(b) f(a1, b,
我不明白为什么 ForEach 中的元素之间有多余的垂直间距在 VStack 里面在 ScrollView 里面使用 GeometryReader 时渲染自定义水平分隔线。 Scrol
我想知道,是否有关于何时使用 session 和 cookie 的指南或最佳实践? 什么应该和什么不应该存储在其中?谢谢! 最佳答案 这些文档很好地了解了 session cookie 的安全问题以及
我在 scipy/numpy 中有一个 Nx3 矩阵,我想用它制作一个 3 维条形图,其中 X 轴和 Y 轴由矩阵的第一列和第二列的值、高度确定每个条形的 是矩阵中的第三列,条形的数量由 N 确定。
假设我用两种不同的方式初始化信号量 sem_init(&randomsem,0,1) sem_init(&randomsem,0,0) 现在, sem_wait(&randomsem) 在这两种情况下
我怀疑该值如何存储在“WORD”中,因为 PStr 包含实际输出。? 既然Pstr中存储的是小写到大写的字母,那么在printf中如何将其给出为“WORD”。有人可以吗?解释一下? #include
我有一个 3x3 数组: var my_array = [[0,1,2], [3,4,5], [6,7,8]]; 并想获得它的第一个 2
我意识到您可以使用如下方式轻松检查焦点: var hasFocus = true; $(window).blur(function(){ hasFocus = false; }); $(win
我是一名优秀的程序员,十分优秀!