# panic和recover

`panic`和`recover`翻译成中文是`恐慌`和`恢复`，我个人感觉比较别扭，还是直接用英文吧。

#### 什么是`panic`

Golang中处理错误的常规方式是使用前面我们讨论的`error`。但是有一些比较特殊的场景，发生错误后我们的程序就无法运行下去了。这时我们通过`panic`来终止程序的执行。当一个函数中发生`panic`时，该函数停止继续执行，然后该函数中被`defer`的函数执行，虽然程序执行控制权转移给外层函数。该过程会冒泡直到`goroutine`级别。然后打印`panic`信息和调用栈并终止整个程序的执行。现在看起来可能有点晦涩难懂，后面我们通过几个简单的例子看下应该会清晰很多。

如果发生了`panic`，我们可以通过`recover`来恢复程序的执行，`panic`和`recover`的关系就像其他编程语言中的`try throw catch`。

#### 什么场景使用`panic`

我们通常应该使用`error`来处理异常情况，除非程序无法继续运行下去了再使用`panic`和`recover`。

以下两种情况比较适合使用`panic`：

1. 有异常情况导致程序无法继续运行下去。

   &#x20;比如我们要实现web服务器，如果端口绑定失败，就可以生成一个`panic`。
2. 编程错误。

   &#x20;比如我们期望一个函数的入参为指针，如果传进来的是`nil`，那么就是编程实现逻辑错误，这时可以生成一个`panic`。

#### `panic`举例

Golang中`panic`函数的签名如下：

```go
func panic(interfce{})
```

传给`panic`函数的入参会在程序终止时打印出来。我们通过例子来理解下。

```go
package main

import "fmt"

func fullName(firstName *string, lastName *string) {
    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}
```

该程序用来打印一个人的姓名的全称。函数`fullName`中判断了`firstName`和`lastName`是否是`nil`。如果是`nil`就调用了`panic`方法，调用时传入了一个字符串用来表示错误信息。该错误信息会在程序终止时打印，该程序执行结果如下：

```
panic: runtime error: last name cannot be nil

goroutine 1 [running]:
main.fullName(0xc000074f68, 0x0)

/Users/goWorkspace/src/test/main.go:10 +0x145
main.main()

/Users/goWorkspace/src/test/main.go:18 +0x4d
exit status 2
```

我们分析下这个打印结果来探究下`panic`的工作原理。我们调用`fullName`时第二个参数传的是`nil`，因此`fullName`内执行了`panic`语句。Golang程序执行过程中如果遇到`panic`就会终止执行，并打印传给`panic`的参数和调用栈。由于`panic`是发生在`fullName`函数内，因此一次打印了`main.fullName(0xc000074f68, 0x0)`和`main.main()`。

#### `defer` + `panic`

**如果一个函数内发生了`panic`，那么会先执行`defer`调用的函数，然后再转移到外层函数。该过程会不断向外冒泡直到`goroutine`级别。然后打印`panic`信息和调用栈。** 前面的例子中没有`defer`函数调用。如果有`defer`函数调用，那么会先执行`defer`调用的函数，然后程序执行控制权再转移到外层函数。下面我们增加个`defer`调用语句。

```go
package main

import "fmt"

func fullName(firstName *string, lastName *string) {
    defer fmt.Println("deferred call in fullName")
    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}
```

该程序执行结果如下：

```
deferred call in fullName
deferred call in main
panic: runtime error: last name cannot be nil

goroutine 1 [running]:
main.fullName(0xc000074f58, 0x0)
        /Users/work/goWorkspace/src/test/main.go:11 +0x1d1
main.main()
        /Users/work/goWorkspace/src/test/main.go:20 +0xa5
exit status 2
```

这段程序中`fullName`发生`panic`时先执行被`defer`的函数调用，打印了`deferred call in fullName`。然后程序执行控制权转移给外层函数，接着执行外层函数被`defer`的函数调用，打印了`deferred call in main`。此时已到`goroutine`级别，因此不会再接着向外冒泡了。接着打印了`panic`信息以及调用栈。

#### `recover`

`recover`跟`panic`一样，也是Golan内置函数。`recover`一般在被`defer`的函数内部调用，用来将程序恢复到正常。`panic`和`recover`跟其他编程需要中的`try catch`非常像，一个用来抛错误，一个用来处理错误并恢复程序的正常执行。`recover`函数签名如下：

```go
func recover() interface{}
```

下面我们通过例子看下`recover`是如何将`panic`的程序恢复到正常执行的。

```go
package main

import "fmt"

func recoverName() {
    if r := recover(); r != nil {
        fmt.Println("recovered from ", r)
    }
}

func fullName(firstName *string, lastName *string) {
    defer recoverName()
    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}
```

这里我们在被`defer`的函数中调用了`recover`来将`panic`恢复到正常。注意`recover()`函数的调用结果即为`panic`的参数。这里我们拿到`panic`的参数后只是进行了打印。该程序执行结果如下：

```
recovered from  runtime error: last name cannot be nil
returned normally from main
deferred call in main
```

#### `recover`只能恢复当前`goroutine`的`panic`

`recover`只能恢复当前`goroutine`的`panic`，不能恢复其他`gorutine`的`panic`。

```go
package main

import (
    "fmt"
    "time"
)

func recovery() {
    if r := recover(); r != nil {
        fmt.Println("recovered:", r)
    }
}

func a() {
    defer recovery()
    fmt.Println("Inside A")
    go b()
    time.Sleep(1 * time.Second)
}

func b() {
    fmt.Println("Inside B")
    panic("oh! B panicked")
}

func main() {
    a()
    fmt.Println("normally returned from main")
}
```

这里在`goroutine` `b`中发生了`panic`，但是函数`a`的执行位于主`goroutine`，跟`b`不是一个，因此函数`a`中的`recover`无法恢复`b`中的`panic`。该程序执行结果如下：

```
Inside A
Inside B
panic: oh! B panicked

goroutine 5 [running]:
main.b()
        /Users/work/goWorkspace/src/test/main.go:23 +0x79
created by main.a
        /Users/work/goWorkspace/src/test/main.go:17 +0x95
exit status 2
```

如果`b`函数调用不是在一个新的`goroutine`，比如我们将`go b()`改为`b()`，则执行结果如下：

```
Inside A
Inside B
recovered: oh! B panicked
normally returned from main
```

这时`b`和`a`的执行都位于主`goroutine`，因此可以正常恢复。

#### 运行时`panic`

在Golang中，有一些运行时错误也会引发`panic`，比如数组访问越界。这种情况相当于我们手动调用`panic`，并传入[runtime.Error](https://golang.org/src/runtime/error.go?s=267:503#L1)类型的参数。接口`runtime.Error`定义如下：

```go
type Error interface {  
    error
    // RuntimeError is a no-op function but
    // serves to distinguish types that are run time
    // errors from ordinary errors: a type is a
    // run time error if it has a RuntimeError method.
    RuntimeError()
}
```

从其定义能够看出来，`runtime.Error`接口也实现了`error`接口。我们通过例子来看下运行时`panic`。

```go
package main

import "fmt"

func a() {
    n := []int{5, 7, 4}
    fmt.Println(n[3])
    fmt.Println("normally returned from a")
}
func main() {
    a()
    fmt.Println("normally returned from main")
}
```

这里我们访问切片`n`的索引为`3`的位置，显然越界了。因此引发运行时`panic`。该程序输出如下：

```
panic: runtime error: index out of range

goroutine 1 [running]:
main.a()
        /Users/work/goWorkspace/src/test/main.go:7 +0x11
main.main()
        /Users/work/goWorkspace/src/test/main.go:11 +0x22
exit status 2
```

运行时`panic`也可以通过`recover`来恢复。

```go
package main

import "fmt"

func r() {
    if r := recover(); r != nil {
        fmt.Println("Recovered", r)
    }
}

func a() {
    defer r()
    n := []int{5, 7, 4}
    fmt.Println(n[3])
    fmt.Println("normally returned from a")
}

func main() {
    a()
    fmt.Println("normally returned from main")
}
```

这里我们在被`defer`的函数`r`中将`panic`的程序恢复正常。该程序输出为：

```
Recovered runtime error: index out of range
normally returned from main
```

#### `recover`后打印调用栈

从前面的几个例子我们会发现，程序`recover`后调用栈就不会被自动打印了。不过我们可以借助[Debug](https://golang.org/pkg/runtime/debug/)包的[PrintStack](https://golang.org/pkg/runtime/debug/#PrintStack)函数来手动打印调用栈。

```go
package main

import (  
    "fmt"
    "runtime/debug"
)

func r() {  
    if r := recover(); r != nil {
        fmt.Println("Recovered", r)
        debug.PrintStack()
    }
}

func a() {  
    defer r()
    n := []int{5, 7, 4}
    fmt.Println(n[3])
    fmt.Println("normally returned from a")
}

func main() {  
    a()
    fmt.Println("normally returned from main")
}
```

这里我们通过`debug.PrintStack()`来手动打印了调用栈。该程序输出如下：

```
Recovered runtime error: index out of range
goroutine 1 [running]:
runtime/debug.Stack(0xc00007a008, 0xc00006ee08, 0x2)
        /usr/local/go/src/runtime/debug/stack.go:24 +0xa7
runtime/debug.PrintStack()
        /usr/local/go/src/runtime/debug/stack.go:16 +0x22
main.r()
        /Users/work/goWorkspace/src/test/main.go:11 +0x9c
panic(0x10a9bc0, 0x115d560)
        /usr/local/go/src/runtime/panic.go:513 +0x1b9
main.a()
        /Users/work/goWorkspace/src/test/main.go:18 +0x3e
main.main()
        /Users/work/goWorkspace/src/test/main.go:23 +0x22
normally returned from main
```

这里我们先恢复了程序的正常，然后紧接着手动打印了调用栈。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://sunwenfei.gitbook.io/sunwenfei/golang/golang-ji-chu-jiao-cheng/cuo-wu/panic-he-recover.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
