Recovery は、パニック情報を取得し、コルーチンの制御を取り戻すために使用される関数です。
ユーザーモードでのみパニックをキャプチャできます。これには、パニック関数のアクティブなトリガー、null ポインター値の受動的呼び出し、範囲外のインデックス、スタック オーバーフロー、読み取り専用メモリの書き換えなどがあります。
Fatalthrow や Fatalpanic などのパニックは、終了を直接呼び出すため、捕捉できませんexit()
。
エラーパニックをキャプチャし、エラースタックを出力
func TestPanic(t *testing.T) {
j := 0
for i := 0; i <= 10; i++ {
func() {
// try except
defer func() {
if err := recover(); err != nil {
fmt.Println("panic recover!")
// 打印错误和堆栈
fmt.Printf("err=%v, stack=%s\n", err, string(debug.Stack()))
}
}()
fmt.Printf("%v\n", i)
x := i / j // 除0报错
fmt.Printf("%v", x)
}()
}
}
上記のコードは、recover がエラーをキャッチできるように、匿名関数を通じて遅延コードとエラー コードをコルーチンに配置します。
Panic は現在の goroutine の遅延のみをトリガーし、recover は defer で呼び出された場合にのみ有効になります。Panic では、複数の呼び出しを defer にネストできます。
コルーチン間で無効化を延期する
func TestPanic1(t *testing.T) {
// 跨协程失效,下面一行的defer不会执行
defer println("in main")
go func() {
defer println("in goroutine")
fmt.Println("子协程running")
panic("子协程崩溃")
}()
time.Sleep(1 * time.Second)
}
サブコルーチンの defer とメイン コルーチンの defer は同じコルーチン内にないため、サブ コルーチンがパニックになった後は、メイン スレッドの defer は実行されません。
リカバリはパニックが発生した後にのみ有効になります
func TestPanic2(t *testing.T) {
defer fmt.Println("in main")
if err := recover(); err != nil {
fmt.Println("occur error")
fmt.Println(err)
}
panic("unknown err")
}
上記のコードでは、エラーが送信される前に、recover が呼び出されているため、recover がエラーをキャッチできません。正しい書き方は、これを defer に入れて呼び出しを遅らせることです。
func TestPanic3(t *testing.T) {
defer fmt.Println("in main")
defer func() {
if err := recover(); err != nil {
fmt.Println("occur error")
fmt.Println(err)
}
}()
panic("unknown err")
}
リカバリの役割は、パニックをキャプチャし、それによって通常のコード実行を復元することです。
Recovery は Defer と一緒に使用する必要があります。
cover はパラメータを渡しませんが、戻り値を持ちます。戻り値は、panic によって渡された値です。
パニックのネストされた呼び出しシーケンス
func TestPanic4(t *testing.T) {
defer fmt.Println("in main")
defer func() {
defer func() {
panic("panic again and again")
}()
panic("panic again")
}()
// 主动触发panic
panic("panic once")
}
// in main
// --- FAIL: TestPanic4 (0.00s)
// panic: panic once
// panic: panic again
// panic: panic again and again [recovered]
// panic: panic again and again
関数 F でパニック ステートメントが記述されてトリガーされると、実行される後続のコードは終了します。パニックが発生した関数 F に実行される遅延関数のリストがある場合は、遅延書き込み順序の逆の順序で実行され、関数 G が関数 F を呼び出すと、関数 F は に戻ります
。パニック後の呼び出し側関数 G。関数 G では、関数 F を呼び出すステートメント以降のステートメントは実行されません。関数 G に実行される defer 関数のリストがある場合は、defer の書き込み順序の逆の順序に従っても問題ありません。
ゴルーチン全体を終了してエラーを報告します。
回復は最後のパニックのみをキャプチャします
func TestPanic5(t *testing.T) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
defer func() {
panic("three")
}()
defer func() {
panic("two")
}()
panic("one")
// three
}
一般的なトリガーパニック状況
配列の添字が範囲外です (実行時エラー)
func TestPanic6(t *testing.T) {
var s []string
fmt.Println(s)
fmt.Println(s[0])
// panic: runtime error: index out of range [0] with length 0 [recovered]
// panic: runtime error: index out of range [0] with length 0
}
Null ポインタ例外 (実行時エラー)
func TestPanic7(t *testing.T) {
type Person struct {
Name string
Age int
}
var p *Person
fmt.Println(p)
fmt.Println(p.Name)
// panic: runtime error: invalid memory address or nil pointer dereference [recovered]
// panic: runtime error: invalid memory address or nil pointer dereference
//
// [signal 0xc0000005 code=0x0 addr=0x0 pc=0xafe05b]
}
型アサーションが失敗しました (インターフェイス変換例外)
func add(a, b interface{
}) {
i := a.(int)
j := b.(int)
fmt.Println(i + j)
}
func TestPanic8(t *testing.T) {
add(20, 18)
add(1, "hello")
// 38
//
// --- FAIL: TestPanic8 (0.00s)
// panic: interface conversion: interface {} is string, not int [recovered]
//
// panic: interface conversion: interface {} is string, not int
}
チャネルは空、チャネルは閉じています (データの書き込み)
func TestPanic9(t *testing.T) {
var ch chan int
close(ch)
// panic: close of nil channel [recovered]
// panic: close of nil channel
}