教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Golang的Panic和Recover

Golang的Panic和Recover

发布时间:2022-01-26   编辑:jiaochengji.com
教程集为您提供Golang的Panic和Recover等资源,欢迎您收藏本站,我们将为您提供最新的Golang的Panic和Recover资源
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"/></svg><h2 id="-panic-">什么是 panic?</h2>

在 Go 语言中,程序中一般是使用错误来处理异常情况。对于程序中出现的大部分异常情况,错误就已经够用了。

但在有些情况,当程序发生异常时,无法继续运行。在这种情况下,我们会使用 <code>panic</code> 来终止程序。当函数发生 panic 时,它会终止运行,在执行完所有的defer函数后,程序控制返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic 信息,接着打印出堆栈跟踪(Stack Trace),最后程序终止。在编写一个示例程序后,我们就能很好地理解这个概念了。

在本教程里,我们还会接着讨论,当程序发生 panic 时,使用 <code>recover</code> 可以重新获得对该程序的控制。

可以认为 <code>panic</code> 和 <code>recover</code> 与其他语言中的 <code>try-catch-finally</code> 语句类似,只不过一般我们很少使用 <code>panic</code> 和 <code>recover</code>。而当我们使用了 <code>panic</code> 和 <code>recover</code> 时,也会比 <code>try-catch-finally</code> 更加优雅,代码更加整洁。

<h2>什么时候应该使用 panic?</h2>

需要注意的是,你应该尽可能地使用错误,而不是使用 panic 和 recover。只有当程序不能继续运行的时候,才应该使用 panic 和 recover 机制。

panic 有两个合理的用例。

<ol><li>发生了一个不能恢复的错误,此时程序不能继续运行。 一个例子就是 web 服务器无法绑定所要求的端口。在这种情况下,就应该使用 panic,因为如果不能绑定端口,啥也做不了。</li><li>发生了一个编程上的错误。 假如我们有一个接收指针参数的方法,而其他人使用 <code>nil</code> 作为参数调用了它。在这种情况下,我们可以使用 panic,因为这是一个编程错误:用 <code>nil</code> 参数调用了一个只能接收合法指针的方法。</li></ol><h2 id="panic-">panic 示例</h2>

内建函数 <code>panic</code> 的签名如下所示:

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">panic</span><span class="hljs-params">(<span class="hljs-keyword">interface</span>{})</span></span>
</li><li>
</li></ol>
</pre>

当程序终止时,会打印传入 <code>panic</code> 的参数。我们写一个示例,你就会清楚它的用途了。我们现在就开始吧。

我们会写一个例子,来展示 <code>panic</code> 如何工作。

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
<span class="hljs-keyword">package</span> main
</li><li>
</li><li>
<span class="hljs-keyword">import</span> (
</li><li>
<span class="hljs-string">"fmt"</span>
</li><li>
)
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fullName</span><span class="hljs-params">(firstName *<span class="hljs-keyword">string</span>, lastName *<span class="hljs-keyword">string</span>)</span></span> {
</li><li>
<span class="hljs-keyword">if</span> firstName == <span class="hljs-literal">nil</span> {
</li><li>
<span class="hljs-built_in">panic</span>( <span class="hljs-string">"runtime error: first name cannot be nil"</span>)
</li><li>
}
</li><li>
<span class="hljs-keyword">if</span> lastName == <span class="hljs-literal">nil</span> {
</li><li>
<span class="hljs-built_in">panic</span>( <span class="hljs-string">"runtime error: last name cannot be nil"</span>)
</li><li>
}
</li><li>
fmt.Printf( <span class="hljs-string">"%s %s\n"</span>, *firstName, *lastName)
</li><li>
fmt.Println( <span class="hljs-string">"returned normally from fullName"</span>)
</li><li>
}
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
</li><li>
firstName := <span class="hljs-string">"Elon"</span>
</li><li>
fullName(&firstName, <span class="hljs-literal">nil</span>)
</li><li>
fmt.Println( <span class="hljs-string">"returned normally from main"</span>)
</li><li>
}
</li></ol>
</pre>

在 playground 上运行

上面的程序很简单,会打印一个人的全名。第 7 行的 <code>fullName</code> 函数会打印出一个人的全名。该函数在第 8 行和第 11 行分别检查了 <code>firstName</code> 和 <code>lastName</code> 的指针是否为 <code>nil</code>。如果是 <code>nil</code>,<code>fullName</code> 函数会调用含有不同的错误信息的 <code>panic</code>。当程序终止时,会打印出该错误信息。

运行该程序,会有如下输出:

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
<span class="hljs-built_in">panic</span>: runtime error: last name cannot be <span class="hljs-literal">nil</span>
</li><li>
</li><li>
goroutine <span class="hljs-number">1</span> [running]:
</li><li>
main.fullName( <span class="hljs-number">0x1040c128</span>, <span class="hljs-number">0x0</span>)
</li><li>
/tmp/sandbox135038844/main. <span class="hljs-keyword">go</span>: <span class="hljs-number">12</span> <span class="hljs-number">0x120</span>
</li><li>
main.main()
</li><li>
/tmp/sandbox135038844/main. <span class="hljs-keyword">go</span>: <span class="hljs-number">20</span> <span class="hljs-number">0x80</span>
</li></ol>
</pre>

我们来分析这个输出,理解一下 panic 是如何工作的,并且思考当程序发生 panic 时,会怎样打印堆栈跟踪。

在第 19 行,我们将 <code>Elon</code> 赋值给了 <code>firstName</code>。在第 20 行,我们调用了 <code>fullName</code> 函数,其中 <code>lastName</code> 等于 <code>nil</code>。因此,满足了第 11 行的条件,程序发生 panic。当出现了 panic 时,程序就会终止运行,打印出传入 panic 的参数,接着打印出堆栈跟踪。因此,第 14 行和第 15 行的代码并不会在发生 panic 之后执行。程序首先会打印出传入 <code>panic</code> 函数的信息:

<pre class="has"><code class="language-Go hljs"><span class="hljs-built_in">panic</span>: runtime error: last name cannot be empty </code>
</pre>

接着打印出堆栈跟踪。

程序在 <code>fullName</code> 函数的第 12 行发生 panic,因此,首先会打印出如下所示的输出。

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
main.fullName( <span class="hljs-number">0x1040c128</span>, <span class="hljs-number">0x0</span>)
</li><li>
/tmp/sandbox135038844/main. <span class="hljs-keyword">go</span>: <span class="hljs-number">12</span> <span class="hljs-number">0x120</span>
</li></ol>
</pre>

接着会打印出堆栈的下一项。在本例中,堆栈跟踪中的下一项是第 20 行(因为发生 panic 的 <code>fullName</code> 调用就在这一行),因此接下来会打印出:

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
main.main()
</li><li>
/tmp/sandbox135038844/main. <span class="hljs-keyword">go</span>: <span class="hljs-number">20</span> <span class="hljs-number">0x80</span>
</li></ol>
</pre>

现在我们已经到达了导致 panic 的顶层函数,这里没有更多的层级,因此结束打印。

<h2 id="-panic-defer">发生 panic 时的 defer</h2>

我们重新总结一下 panic 做了什么。当函数发生 panic 时,它会终止运行,在执行完所有的延迟函数后,程序控制返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic 信息,接着打印出堆栈跟踪,最后程序终止

在上面的例子中,我们没有延迟调用任何函数。如果有延迟函数,会先调用它,然后程序控制返回到函数调用方。

我们来修改上面的示例,使用一个延迟语句。

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
<span class="hljs-keyword">package</span> main
</li><li>
</li><li>
<span class="hljs-keyword">import</span> (
</li><li>
<span class="hljs-string">"fmt"</span>
</li><li>
)
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fullName</span><span class="hljs-params">(firstName *<span class="hljs-keyword">string</span>, lastName *<span class="hljs-keyword">string</span>)</span></span> {
</li><li>
<span class="hljs-keyword">defer</span> fmt.Println( <span class="hljs-string">"deferred call in fullName"</span>)
</li><li>
<span class="hljs-keyword">if</span> firstName == <span class="hljs-literal">nil</span> {
</li><li>
<span class="hljs-built_in">panic</span>( <span class="hljs-string">"runtime error: first name cannot be nil"</span>)
</li><li>
}
</li><li>
<span class="hljs-keyword">if</span> lastName == <span class="hljs-literal">nil</span> {
</li><li>
<span class="hljs-built_in">panic</span>( <span class="hljs-string">"runtime error: last name cannot be nil"</span>)
</li><li>
}
</li><li>
fmt.Printf( <span class="hljs-string">"%s %s\n"</span>, *firstName, *lastName)
</li><li>
fmt.Println( <span class="hljs-string">"returned normally from fullName"</span>)
</li><li>
}
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
</li><li>
<span class="hljs-keyword">defer</span> fmt.Println( <span class="hljs-string">"deferred call in main"</span>)
</li><li>
firstName := <span class="hljs-string">"Elon"</span>
</li><li>
fullName(&firstName, <span class="hljs-literal">nil</span>)
</li><li>
fmt.Println( <span class="hljs-string">"returned normally from main"</span>)
</li><li>
}
</li></ol>
</pre>

在 playground 上运行

上述代码中,我们只修改了两处,分别在第 8 行和第 20 行添加了延迟函数的调用。

该函数会打印:

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
This program prints,
</li><li>
</li><li>
deferred call in fullName
</li><li>
deferred call in main
</li><li>
<span class="hljs-built_in">panic</span>: runtime error: last name cannot be <span class="hljs-literal">nil</span>
</li><li>
</li><li>
goroutine <span class="hljs-number">1</span> [running]:
</li><li>
main.fullName( <span class="hljs-number">0x1042bf</span>90, <span class="hljs-number">0x0</span>)
</li><li>
/tmp/sandbox060731990/main. <span class="hljs-keyword">go</span>: <span class="hljs-number">13</span> <span class="hljs-number">0x280</span>
</li><li>
main.main()
</li><li>
/tmp/sandbox060731990/main. <span class="hljs-keyword">go</span>: <span class="hljs-number">22</span> <span class="hljs-number">0xc0</span>
</li></ol>
</pre>

当程序在第 13 行发生 panic 时,首先执行了延迟函数,接着控制返回到函数调用方,调用方的延迟函数继续运行,直到到达顶层调用函数。

在我们的例子中,首先执行 <code>fullName</code> 函数中的 <code>defer</code> 语句(第 8 行)。程序打印出:

<pre class="has"><code class="language-Go hljs">deferred call in fullName </code>
</pre>

接着程序返回到 <code>main</code> 函数,执行了 <code>main</code> 函数的延迟调用,因此会输出:

<pre class="has"><code class="hljs sql">deferred <span class="hljs-keyword">call</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">main</span> </code>
</pre>

现在程序控制到达了顶层函数,因此该函数会打印出 panic 信息,然后是堆栈跟踪,最后终止程序。

<h2 id="recover">recover</h2>

<code>recover</code> 是一个内建函数,用于重新获得 panic 协程的控制。

<code>recover</code> 函数的标签如下所示:

<pre class="has"><code class="language-Go hljs"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">recover</span><span class="hljs-params">()</span> <span class="hljs-title">interface</span></span>{} </code>
</pre>

只有在延迟函数的内部,调用 <code>recover</code> 才有用。在延迟函数内调用 <code>recover</code>,可以取到 <code>panic</code> 的错误信息,并且停止 panic 续发事件(Panicking Sequence),程序运行恢复正常。如果在延迟函数的外部调用 <code>recover</code>,就不能停止 panic 续发事件。

我们来修改一下程序,在发生 panic 之后,使用 <code>recover</code> 来恢复正常的运行。

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
<span class="hljs-keyword">package</span> main
</li><li>
</li><li>
<span class="hljs-keyword">import</span> (
</li><li>
<span class="hljs-string">"fmt"</span>
</li><li>
)
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">recoverName</span><span class="hljs-params">()</span></span> {
</li><li>
<span class="hljs-keyword">if</span> r := <span class="hljs-built_in">recover</span>(); r!= <span class="hljs-literal">nil</span> {
</li><li>
fmt.Println( <span class="hljs-string">"recovered from "</span>, r)
</li><li>
}
</li><li>
}
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fullName</span><span class="hljs-params">(firstName *<span class="hljs-keyword">string</span>, lastName *<span class="hljs-keyword">string</span>)</span></span> {
</li><li>
<span class="hljs-keyword">defer</span> recoverName()
</li><li>
<span class="hljs-keyword">if</span> firstName == <span class="hljs-literal">nil</span> {
</li><li>
<span class="hljs-built_in">panic</span>( <span class="hljs-string">"runtime error: first name cannot be nil"</span>)
</li><li>
}
</li><li>
<span class="hljs-keyword">if</span> lastName == <span class="hljs-literal">nil</span> {
</li><li>
<span class="hljs-built_in">panic</span>( <span class="hljs-string">"runtime error: last name cannot be nil"</span>)
</li><li>
}
</li><li>
fmt.Printf( <span class="hljs-string">"%s %s\n"</span>, *firstName, *lastName)
</li><li>
fmt.Println( <span class="hljs-string">"returned normally from fullName"</span>)
</li><li>
}
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
</li><li>
<span class="hljs-keyword">defer</span> fmt.Println( <span class="hljs-string">"deferred call in main"</span>)
</li><li>
firstName := <span class="hljs-string">"Elon"</span>
</li><li>
fullName(&firstName, <span class="hljs-literal">nil</span>)
</li><li>
fmt.Println( <span class="hljs-string">"returned normally from main"</span>)
</li><li>
}
</li></ol>
</pre>

在 playground 上运行

在第 7 行,<code>recoverName()</code> 函数调用了 <code>recover()</code>,返回了调用 <code>panic</code> 的传参。在这里,我们只是打印出 <code>recover</code> 的返回值(第 8 行)。在 <code>fullName</code> 函数内,我们在第 14 行延迟调用了 <code>recoverNames()</code>。

当 <code>fullName</code> 发生 panic 时,会调用延迟函数 <code>recoverName()</code>,它使用了 <code>recover()</code> 来停止 panic 续发事件。

该程序会输出:

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
recovered from runtime error: last name cannot be <span class="hljs-literal">nil</span>
</li><li>
returned normally from main
</li><li>
deferred call in main
</li></ol>
</pre>

当程序在第 19 行发生 panic 时,会调用延迟函数 <code>recoverName</code>,它反过来会调用 <code>recover()</code> 来重新获得 panic 协程的控制。第 8 行调用了 <code>recover</code>,返回了 <code>panic</code> 的传参,因此会打印:

<pre class="has"><code class="hljs delphi">recovered from runtime error: last <span class="hljs-keyword">name</span> cannot be <span class="hljs-keyword">nil</span> </code>
</pre>

在执行完 <code>recover()</code> 之后,panic 会停止,程序控制返回到调用方(在这里就是 <code>main</code> 函数),程序在发生 panic 之后,从第 29 行开始会继续正常地运行。程序会打印 <code>returned normally from main</code>,之后是 <code>deferred call in main</code>。

<h2 id="panic-recover-go-">panic,recover 和 Go 协程</h2>

只有在相同的 Go协程中调用 recover 才管用。<code>recover</code> 不能恢复一个不同协程的 panic。我们用一个例子来理解这一点。

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
<span class="hljs-keyword">package</span> main
</li><li>
</li><li>
<span class="hljs-keyword">import</span> (
</li><li>
<span class="hljs-string">"fmt"</span>
</li><li>
<span class="hljs-string">"time"</span>
</li><li>
)
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">recovery</span><span class="hljs-params">()</span></span> {
</li><li>
<span class="hljs-keyword">if</span> r := <span class="hljs-built_in">recover</span>(); r != <span class="hljs-literal">nil</span> {
</li><li>
fmt.Println( <span class="hljs-string">"recovered:"</span>, r)
</li><li>
}
</li><li>
}
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">a</span><span class="hljs-params">()</span></span> {
</li><li>
<span class="hljs-keyword">defer</span> recovery()
</li><li>
fmt.Println( <span class="hljs-string">"Inside A"</span>)
</li><li>
<span class="hljs-keyword">go</span> b()
</li><li>
time.Sleep( <span class="hljs-number">1</span> * time.Second)
</li><li>
}
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">b</span><span class="hljs-params">()</span></span> {
</li><li>
fmt.Println( <span class="hljs-string">"Inside B"</span>)
</li><li>
<span class="hljs-built_in">panic</span>( <span class="hljs-string">"oh! B panicked"</span>)
</li><li>
}
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
</li><li>
a()
</li><li>
fmt.Println( <span class="hljs-string">"normally returned from main"</span>)
</li><li>
}
</li></ol>
</pre>

在 playground 上运行

在上面的程序中,函数 <code>b()</code> 在第 23 行发生 panic。函数 <code>a()</code> 调用了一个延迟函数 <code>recovery()</code>,用于恢复 panic。在第 17 行,函数 <code>b()</code> 作为一个不同的协程来调用。下一行的 <code>Sleep</code> 只是保证 <code>a()</code> 在 <code>b()</code> 运行结束之后才退出。

你认为程序会输出什么?panic 能够恢复吗?答案是否定的,panic 并不会恢复。因为调用 <code>recovery</code> 的协程和 <code>b()</code> 中发生 panic 的协程并不相同,因此不可能恢复 panic。

运行该程序会输出:

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
Inside A
</li><li>
Inside B
</li><li>
<span class="hljs-built_in">panic</span>: oh! B panicked
</li><li>
</li><li>
goroutine <span class="hljs-number">5</span> [running]:
</li><li>
main.b()
</li><li>
/tmp/sandbox388039916/main. <span class="hljs-keyword">go</span>: <span class="hljs-number">23</span> <span class="hljs-number">0x80</span>
</li><li>
created by main.a
</li><li>
/tmp/sandbox388039916/main. <span class="hljs-keyword">go</span>: <span class="hljs-number">17</span> <span class="hljs-number">0xc0</span>
</li></ol>
</pre>

从输出可以看出,panic 没有恢复。

如果函数 <code>b()</code> 在相同的协程里调用,panic 就可以恢复。

如果程序的第 17 行由 <code>go b()</code> 修改为 <code>b()</code>,就可以恢复 panic 了,因为 panic 发生在与 recover 相同的协程里。如果运行这个修改后的程序,会输出:

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
Inside A
</li><li>
Inside B
</li><li>
recovered: oh! B panicked
</li><li>
normally returned from main
</li></ol>
</pre> <h2 id="-panic">运行时 panic</h2>

运行时错误(如数组越界)也会导致 panic。这等价于调用了内置函数 <code>panic</code>,其参数由接口类型<code>runtime.Error</code>给出。<code>runtime.Error</code> 接口的定义如下:

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
<span class="hljs-keyword">type</span> Error <span class="hljs-keyword">interface</span> {
</li><li>
error
</li><li>
<span class="hljs-comment">// RuntimeError is a no-op function but</span>
</li><li>
<span class="hljs-comment">// serves to distinguish types that are run time</span>
</li><li>
<span class="hljs-comment">// errors from ordinary errors: a type is a</span>
</li><li>
<span class="hljs-comment">// run time error if it has a RuntimeError method.</span>
</li><li>
RuntimeError()
</li><li>
}
</li></ol>
</pre>

而 <code>runtime.Error</code> 接口满足内建接口类型 <code>error</code>

我们来编写一个示例,创建一个运行时 panic。

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
<span class="hljs-keyword">package</span> main
</li><li>
</li><li>
<span class="hljs-keyword">import</span> (
</li><li>
<span class="hljs-string">"fmt"</span>
</li><li>
)
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">a</span><span class="hljs-params">()</span></span> {
</li><li>
n := [] <span class="hljs-keyword">int</span>{ <span class="hljs-number">5</span>, <span class="hljs-number">7</span>, <span class="hljs-number">4</span>}
</li><li>
fmt.Println(n[ <span class="hljs-number">3</span>])
</li><li>
fmt.Println( <span class="hljs-string">"normally returned from a"</span>)
</li><li>
}
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
</li><li>
a()
</li><li>
fmt.Println( <span class="hljs-string">"normally returned from main"</span>)
</li><li>
}
</li></ol>
</pre>

在 playground 上运行

在上面的程序中,第 9 行我们试图访问 <code>n[3]</code>,这是一个对切片的错误引用。该程序会发生 panic,输出如下:

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
<span class="hljs-built_in">panic</span>: runtime error: index out of <span class="hljs-keyword">range</span>
</li><li>
</li><li>
goroutine <span class="hljs-number">1</span> [running]:
</li><li>
main.a()
</li><li>
/tmp/sandbox780439659/main. <span class="hljs-keyword">go</span>: <span class="hljs-number">9</span> <span class="hljs-number">0x40</span>
</li><li>
main.main()
</li><li>
/tmp/sandbox780439659/main. <span class="hljs-keyword">go</span>: <span class="hljs-number">13</span> <span class="hljs-number">0x20</span>
</li></ol>
</pre>

你也许想知道,是否可以恢复一个运行时 panic?当然可以!我们来修改一下上面的代码,恢复这个 panic。

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
<span class="hljs-keyword">package</span> main
</li><li>
</li><li>
<span class="hljs-keyword">import</span> (
</li><li>
<span class="hljs-string">"fmt"</span>
</li><li>
)
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">r</span><span class="hljs-params">()</span></span> {
</li><li>
<span class="hljs-keyword">if</span> r := <span class="hljs-built_in">recover</span>(); r != <span class="hljs-literal">nil</span> {
</li><li>
fmt.Println( <span class="hljs-string">"Recovered"</span>, r)
</li><li>
}
</li><li>
}
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">a</span><span class="hljs-params">()</span></span> {
</li><li>
<span class="hljs-keyword">defer</span> r()
</li><li>
n := [] <span class="hljs-keyword">int</span>{ <span class="hljs-number">5</span>, <span class="hljs-number">7</span>, <span class="hljs-number">4</span>}
</li><li>
fmt.Println(n[ <span class="hljs-number">3</span>])
</li><li>
fmt.Println( <span class="hljs-string">"normally returned from a"</span>)
</li><li>
}
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
</li><li>
a()
</li><li>
fmt.Println( <span class="hljs-string">"normally returned from main"</span>)
</li><li>
}
</li></ol>
</pre>

在 playground 上运行

运行上面程序会输出:

<pre class="has"><code class="hljs delphi"/> <ol class="hljs-ln"><li>
Recovered runtime error: <span class="hljs-keyword">index</span> <span class="hljs-keyword">out</span> <span class="hljs-keyword">of</span> range
</li><li>
normally returned from main
</li></ol>
</pre>

从输出可以知道,我们已经恢复了这个 panic。

<h2 id="-">恢复后获得堆栈跟踪</h2>

当我们恢复 panic 时,我们就释放了它的堆栈跟踪。实际上,在上述程序里,恢复 panic 之后,我们就失去了堆栈跟踪。

有办法可以打印出堆栈跟踪,就是使用 Debug包中的 PrintStack函数。

<pre class="has"><code class="language-Go hljs"/> <ol class="hljs-ln"><li>
<span class="hljs-keyword">package</span> main
</li><li>
</li><li>
<span class="hljs-keyword">import</span> (
</li><li>
<span class="hljs-string">"fmt"</span>
</li><li>
<span class="hljs-string">"runtime/debug"</span>
</li><li>
)
</li><li>
</li><li>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">r</span><span class="hljs-params">()</span></span> {
</li><li>
<span class="hljs-keyword">if</span> r := <span class="hljs-built_in">recover</span>(); r != <span class="hljs-literal">nil</span> {
</li><li>
fmt.Println( <span class="hljs-string">"Recovered"</span>, r)
</li><li>
debug.PrintStack()
</li><li>
}
</li><li>
}
</li><li>
</li><li>
[关闭]
~ ~