📕
innohub
  • KEEP LEARNING
  • WebInfo
    • JS 部分运算符操作
    • javascript 中的object array
    • 错误处理以及异常捕获
    • JavaScript Bases
    • LaoZi & Confucius
  • PyInfo
    • Python3 输入与输出
    • Python3OS
    • python 修饰器的使用
    • python3 与mysql数据库连接使用实例
    • Format-specifier
    • CGI初学踩坑
    • Django 项目测试
    • Assert-info
    • 使用ngnix和uWSGI配置Django
    • write|SVN​
    • Matplotlib 基本使用
    • 重读 Python 官方文档
    • Python3 Base
    • python3 多线程
    • Python3 函数解析
    • python3 str 对象的基本操作
    • protocl buffers
    • Iterator-Generator
    • Django base
    • Decorator 2
    • Export to wheel or egg-info
    • 三. 运算、数据及逻辑
  • GoInfo
    • Info
      • Select 语句使用
      • First class function
      • Work Pools
      • StructTag
      • Go struct
      • 常用函数
      • Strings操作
      • Golang Bases
      • 数组以及切片
      • 文件操作
      • Golang 中的指针类型
      • Golang Map 类型
      • Reflection
      • 函数闭包
      • 接口
      • Panic and Recover
      • Go中的错误处理
      • 并发
      • defer usage
      • Method in golang
      • Object-oriented Programming
      • Goalng 包管理机制
  • RustInfo
    • Info
      • 包、crate、模块
      • Rust 中的错误处理
      • 智能指针
      • 泛型 generics
      • 数据布局与内存对齐
      • Functions and methods
      • 无畏并发
      • Actix-web 基本使用
      • Got from guessing game
      • 结构体的使用
      • Rust 中的函数式语言功能
      • 集合类型
      • 枚举以及模式匹配
      • Slice 类型
      • 生命周期
      • 测试
      • Rust 中的所有权
      • trait
      • 不安全 Rust
      • Format_print
      • Rust 通用编程概念
      • Macro
  • OS
    • info
      • 内存屏障 -- Part 1
      • 内存屏障 -- Part 2
      • CPU 上下文切换
      • 文件读写与零拷贝
      • ELF 文件
  • MySql
    • info
      • MySql 架构与历史
      • 02-key
  • kubernetes
    • 第二章 k8s 基本概念
    • 第一章 Kubernetes -- 伟大舵手
  • Redis
    • info
      • Redis 总览
      • 02-underline-ds
  • Shell&CInfo
    • GCC 与make的使用
    • C++ 中 malloc 与 new
    • 位运算符操作
    • Base of C macro
    • C 语言中 extern 关键字的用法
  • Distributed
    • info
      • 分布式理论概述
  • Java
    • info
      • Java 内存模型
  • Prometheus
    • Prometheus -- 不灭之火
Powered by GitBook
On this page
  • 1. Panic ,recover关系
  • 2. 使用panic
  • 2.1 panic使用
  • 2.2 panicking 中使用defer
  • 3. Recover
  • 3.1 panic , recover, goroutines
  • 4. 运行时错误
  • 4.1 获取栈信息

Was this helpful?

  1. GoInfo
  2. Info

Panic and Recover

[TOC]

1. Panic ,recover关系

在go中处理不正常状况的最惯用的方法是使用 errors ,错误可以满足大多数的异常处理场景。但是还是有很多状况,使得程序不可以简单地在一个异常后继续执行。在这时就需要一个panic来终止程序。当一个panic发生时,程序的运行终止,所有的deferred操作都被依次执行,之后程序的控制权返回给调用者。该过程持续进行直到当前goroutines 的所有函数都返回。这时程序根据栈顺序(stack trace)打印panix信息然后终止。

也可以通过recover操作从一个panic中恢复该goroutine。panic以及recover可以被认为与 try-catch-finally 的操作机制相似。可以使用这种方法使得程序更加简洁。

2. 使用panic

注意:需要特别谨慎使用panic,只有当程序无法继续执行时才可以使用panic,否则使用errors进行处理。一般具有以下两种使用panic的样例:

  • 无法恢复的错误,使得程序无法简单的继续执行。一个例子:当一个web服务器没有成功绑定到一个要求的端口,此时就需要使用一个panic因为绑定失败无法进行下一部动作

  • 编程错误。当有一个method接受一个指针类型的接收器,但是使用一个nil类型变量调用该方法。

2.1 panic使用

func panic(v interface{})
    内置函数panic,当一个函数F调用panic函数时,停止当前进程的正常执行,F立即停止执行。被F deferred 的函数按栈顺序正常执行。之后函数F返回到调用者。
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")
}
/*panic: runtime error: last name cannot be nil

goroutine 1 [running]:  
main.fullName(0x1040c128, 0x0)  
    /tmp/sandbox135038844/main.go:12 +0x120
main.main()  
    /tmp/sandbox135038844/main.go:20 +0x80*/

在使用fullName式产生了panic,一个运行时错误,此时程序的运行终止。根据函数的调用栈,依次打印main.fullName, main.main() 分别代表maingoroutine的fullName, main方法。

2.2 panicking 中使用defer

使用defer语句,当一个panic发生时,在程序返回之前一定会调用defer指向的语句,相当于其他语言中的finally。

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(0x1042bf90, 0x0)  
    /tmp/sandbox060731990/main.go:13 +0x280
main.main()  
    /tmp/sandbox060731990/main.go:22 +0xc0*/

调用fullname中使用了defer语句,所以先出栈,先执行该语句,然后是main中的,最后打印panic信息以及调用栈。

3. Recover

func recover() interface{}
    内置函数recover用于管理一个panicked goroutine的行为。在一个deferred函数中执行一个调用使得该goroutine恢复运行。该函数停止panicking序列通过恢复正常的执行,并把错误信息传递给票panic的调用者。注意,recover必须在一个deferred语句中使用,否则无法恢复正常运行。
返回值:当panic的参数为nil,或者recover没有在referred函数中使用,返回nil
否则返回该gouroutine是否正确恢复

注意使用recover时必须在一个deferred函数中使用。

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")
}

recover from runtime error: the lastName is nil
normally returned from main
defered call from main routine

函数recoverName()调用了recover()函数,返回值传递给panic的调用者。现在发生了panic,然后按栈顺序调用deferred函数,所以先执行recoverName()函数,该函数使用了recover()返回给main.main然后程序继续执行,所以打印最后的语句;然后执行下一个deferred函数。

3.1 panic , recover, goroutines

如果使用不同goroutines的recover函数,不可以使得当前goroutine恢复。

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中的recover函数无法进行恢复。在a中等待1s使得程序不会在a中中断。如果将go b()改为b()那么程序就可以从panic中恢复。、

4. 运行时错误

panic也可能发生在运行时刻,比如数组越界,变量除0错(注意直接硬码除0错可以被编译器检测,属于语法错误)。关于运行时错误,等价于调用内置函数panic,使用一个runtime.Error类型的参数。有关runtime.Error接口的定义如下:

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

该接口嵌套了error接口,所以可以使用fmt.Print函数隐式调用Error()方法输出错误信息。RuntimeError()方法没有操作,只是用来区分运行时错误以及普通错误。

运行时错误,也可以使用recover函数进行恢复。

import "fmt"

func outOfBound() {
  defer recovery()
  a := []int{1, 2, 0}
  fmt.Println(a, a[1]/a[2])
  fmt.Println("normally returned from outOfBound")
}

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

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

4.1 获取栈信息

如果从一个panic中恢复了goroutine,那么就会失去错误的栈信息(stack trace),可以使用runtime/debug包中的PrintStack()函数获取相应的栈信息。

func r() {  
    if r := recover(); r != nil {
        fmt.Println("Recovered", r)
        debug.PrintStack()
    }
}
Previous接口NextGo中的错误处理

Last updated 5 years ago

Was this helpful?