defer, panic, recover

defer, panic, recover #

热身样例 #

package main

import (
	"fmt"
	"time"
)

func main() {
	G()
	fmt.Println("==============================")
	G1()
	fmt.Println("==============================")
	G2() // will raise panic in goroutine
	fmt.Println("==============================")

	defer func() {
		if err := recover(); err != nil {
			fmt.Println("recover from : ", err)
		}
	}()

	defer panic("The third panic")
	defer panic("The second panic")
	defer panic("The first panic")

	panic("main panic")
}

func G() {
	defer func() {
		fmt.Println("c")
	}()

	F()

	fmt.Println("continue in G()")
}

func F() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("capture err in F(): ", err)
		}
		fmt.Println("b")
	}()
	panic("panic in F()")
}

func G1() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("capture err in G1(): ", err)
		}
		fmt.Println("c1")
	}()

	F1()

	fmt.Println("continue in G1()")
}

func F1() {
	defer func() {
		fmt.Println("b1")
	}()

	panic("panic in F1()")
}


// try to capture from the outside goroutine
func G2() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("capture err in G2(): ", err)
		}
		fmt.Println("c2")
	}()

	go F2()

	fmt.Println("a2")
	time.Sleep(time.Second)
}

func F2() {
	defer func() {
		fmt.Println("b2")
	}()

	panic("panic in F2()")
}

Actual output:

capture err in F():  panic in F()
b
continue in G()
c
==============================
b1
capture err in G1():  panic in F1()
c1

Defer 原则 #

defer 语句三大规则:

  1. defer 语句中的变量在“注册”defer 函数时即已经确定。
  2. defer 的函数以 LIFO 的顺序运行。
  3. defer 函数可以读写外围函数中预定义的返回值。

看看这个例子,你认为的输出是什么?

func main() {
	i := 0

	defer fmt.Println("a: ", i) // 0

	defer func(i int) {
		fmt.Println("b: ", i) // 0
	}(i)

	defer func() {
		fmt.Println("c: ", i) // 1
	}()

	i++
}

在运行 defer 语句时,Go 会将语句涉及的外部变量全部建立拷贝,因此例子中的 a 为 0 。如果希望规避这个特性,让 defer 中的函数应用外部修改,则可以给 defer 传入匿名函数。虽然函数也是值传递,但传递的是函数指针,因此 c 可以获取到外部修改。

此处解释了原因: https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-defer/#%E9%A2%84%E8%AE%A1%E7%AE%97%E5%8F%82%E6%95%B0

panic:当函数 F 调用 panic ,则 F 的执行会被停止,F 内所有已注册的 defer 函数会被依次执行,最后把 F 返回给它的调用者。

recover:仅在 defer 函数中有效。若当前 goroutine 正在 panic,则调用 recover 会获取到 panic 内容,并继续代码的运行(不会返回 panic 调用点,而是继续运行 defer functions)。若当前 goroutine 一切正常,则只返回 nil

总结 #

  1. 在抛出 panic 的函数中 defer function 能够运行,defer 中的 recover 能够捕获到 panic ,但不会使函数继续运行。

    示例代码
    func G() {
        defer func() {
            fmt.Println("c")  // 5
        }()
       
        F()
       
        fmt.Println("continue in G()")  // 4
    }
       
    func F() {
        defer func() {
            if err := recover(); err != nil {
                fmt.Println("capture err in F(): ", err)  // 2
            }
            fmt.Println("b")  // 3
        }()
        panic("panic in F()")  // 1
            fmt.Println("after panic")
    }
    
  2. 在抛出 panic 的函数中 defer function 能够运行,且父函数的 defer function 也能够运行,父函数的 defer 中 recover 能够捕获到 panic 并恢复,但不会继续运行。

    示例代码
    func G1() {
        defer func() {
            if err := recover(); err != nil {
                fmt.Println("capture err in G1(): ", err) // 4
            }
            fmt.Println("c1") // 5
        }()
       
        F1()
        // 3 - get a panic in here
       
        fmt.Println("continue in G1()")
    }
       
    func F1() {
        defer func() {
            fmt.Println("b1") // 2
        }()
       
        panic("panic in F1()") // 1
        fmt.Println("after panic")
    }
    
  3. 若在 goroutine 协程抛出 panic,则外部函数无法捕获到协程内抛出的 panic。

    示例代码
    // try to capture from the outside goroutine
    func G2() {
        defer func() {
            if err := recover(); err != nil {
                fmt.Println("capture err in G2(): ", err)
            }
            fmt.Println("c2")
        }()
       
        go F2()
       
        fmt.Println("a2") // 1
        time.Sleep(time.Second)
    }
       
    func F2() {
        defer func() {
            fmt.Println("b2") // 3
        }()
       
        panic("panic in F2()") // 2
    }
    
  4. 若 goroutine 中出现 panic 后,defer 函数又继续 panic,则最后的 recover 函数捕获的是最后一次 panic 的信息。

    示例代码
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("recover from : ", err) // 5
        }
        fmt.Println("end") // 6
        if err := recover(); err != nil {
            fmt.Println("recover from : ", err)
        }
    }()
    defer panic("The third panic")  // 4
    defer panic("The second panic") // 3
    defer panic("The first panic")  // 2
    
    panic("main panic") // 1
    fmt.Println("after main panic")
    

References #