一:defer
延迟处理,倒序执行
能够触发defer的是遇见return(或函数体到末尾)和遇见panic
func f1() int {
x := 5
defer func() {
x++
}()
return x
}
func f2() (x int) {
defer func() {
x++
}()
return 5
}
func f3() (y int) {
x := 5
defer func() {
x++
}()
return x
}
//defer中的x是局部变量 所以……
func f4() (x int) {
defer func(x int) {
x++ //更改的是局部变量
}(x) //传进去的是0
return 5
}
func main() {
fmt.Println(f1()) //5
fmt.Println(f2()) //6
fmt.Println(f3()) //5
fmt.Println(f4()) //5
}
defer在压栈的时候就已经保存了参数的值!!!
f4()中,在defer前 打印x的值 发现是0,所以传入defer中的x 是0,而且x++ 更改的是局部变量。。如果改成下边这样 结果就是6了
func f4() (x int) {
defer func(y int) {
x++
}(x)
return 5
}
//使用 defer 只是延时调用函数,此时传递给函数里的变量,不应该受到后续程序的影响。
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y))
x = 10
defer calc("BB", x, calc("B", x, y))
y = 20
}
//A 1 2 3
//B 10 2 12
//BB 10 12 22
//AA 1 3 4
func DeferFunc4() (t int) {
defer func(i int) {
fmt.Println(i)
fmt.Println(t)
}(t)
t = 1
return 2
}
fmr.Println(DeferFunc4())
//0
//2
type Car struct {
model string
}
func (c Car) PrintModel() {
fmt.Println(c.model)
}
func main() {
c := Car{model: "DeLorean DMC-12"}
defer c.PrintModel()
c.model = "Chevrolet Impala"
}
程序输出程序输出DeLorean DMC-12,defer的时候会把函数和参考拷贝一份保存起来,所以c.model的值后面改变也不会影响defer的运行。
如果改成指针接收者,那结果就是Chevrolet Impala,这些defer虽然将函数和参数保存了起来,但是由于参数的值本身是指针,随意后面的改动会影响到defer函数的行为。
Golang官方blog里总结了三条defer的行为规则。(创建defer的函数为主函数,defer语句后面的函数成为延迟函数。)
规则一:延迟函数的参数在defer语句出现的时候就已经确定下来了。
规则二:延迟函数执行按后进先出顺序执行,即先出现的defer后执行。
规则三:延迟函数可能操作主函数的具名返回值。
二:panic
panic是内建的停止控制流的函数。相当于其他编程语言的抛异常操作。当函数F调用了panic,F的执行会被停止,在F中panic前面定义的defer操作都会被执行,然后F函数返回。
遇到panic时,遍历本协程的defer链表,并执行defer,在执行defer过程中:遇到recover则停止panic,返回recover处继续往下执行。如果没有遇到recover,则遍历完本协程的defer链表后向stderr抛出panic信息。
当defer遇见panic,但是并不捕获异常的情况
func main() {
defer_call()
fmt.Println("main 正常结束")
}
func defer_call() {
defer func() { fmt.Println("defer: panic 之前1") }()
defer func() { fmt.Println("defer: panic 之前2") }()
panic("异常内容") //触发defer出栈
defer func() { fmt.Println("defer: panic 之后,永远执行不到") }()
}
/*
defer: panic 之前2
defer: panic 之前1
panic: 异常内容
......
*/
defer遇见panic,并捕获异常
func main() {
defer_call()
fmt.Println("main 正常结束")
}
func defer_call() {
defer func() {
fmt.Println("defer: panic 之前1, 捕获异常")
if err := recover(); err != nil {
fmt.Println(err)
}
}()
defer func() { fmt.Println("defer: panic 之前2, 不捕获") }()
panic("异常内容") //触发defer出栈
defer func() { fmt.Println("defer: panic 之后, 永远执行不到") }()
fmt.Println("panic 之后, 永远执行不到")
}
/*
defer: panic 之前2, 不捕获
defer: panic 之前1, 捕获异常
异常内容
main 正常结束
*/
defer中包含panic
func main() {
defer func() {
if err := recover(); err != nil{
fmt.Println(err)
}else {
fmt.Println("fatal")
}
}()
defer func() {
panic("defer panic")
}()
panic("panic")
fmt.Println("panic 之后, 永远执行不到")
}
/*
defer panic
*/
panic之后的内容(包括defer)是始终不会执行到的
三:panic和recover
Go语言中目前(Go1.12)是没有异常机制,但是使用panic/recover模式来处理错误,panic可以在任何地方引发,但recover只有在defer调用的函数中有效。
可恢复的 panic 必须要 recover 的配合,并且这个 recover 必须位于同一 goroutine 的直接调用链上,否则无法对 panic 进行恢复。
当一个 panic 被恢复后,调度并因此中断,会重新进入调度循环,进而继续执行 recover 后面的代码, 包括比 recover 更早的 defer(因为已经执行过得 defer 已经被释放, 而尚未执行的 defer 仍在 goroutine 的 defer 链表中),或者 recover 所在函数的调用方。
例子1:
func A () {
B()
C()
}
func B() {
defer func () {
recover() // 无法恢复 panic("C")
}()
println("B")
}
func C() {
panic("C")
}
A 调用B和C,B里边的recover 对于C中的panic 不起作用,因为A--> B和 A--->C是分叉的,不在直接调用链上
例子2:
func A () {
defer func () {
recover() // 可以恢复 panic("C")
}()
B()
}
func B() {
C()
}
func C() {
panic("C")
}
A-->B--->C,A里边的recover 对于C中的panic 是能起作用的,因为在同一个调用链上
例子3:
package main
import (
"fmt"
)
func main() {
requests := []int{12, 2, 3, 41, 5, 6, 1, 12, 3, 4, 2, 31}
for n := range requests {
go run(n) //开启多个协程
}
for {
select {}
}
}
func run(num int) {
defer func() {
if err := recover();err != nil {
fmt.Printf("%s\n", err)
}
}()
if num%5 == 0 {
panic("请求出错")
}
fmt.Printf("%d\n", num)
}
//
func run2(num int) {
defer func() {
if err := recover(); err != nil {
fmt.Printf("%s\n", err)
}
}()
if num%5 == 0 {
panic("请求出错")
}
go myPrint(num)
}
func myPrint(num int) {
if num%4 == 0 {
panic("请求又出错了")
}
fmt.Printf("%d\n", num)
}
run()中的panic能被recover接收,但是run2中, myPrint()函数中产生的panic是不能被run2中的recover()接收的。。。因为他们不在同一个goroutinue中
例子4:
func funcA() {
fmt.Println("func A")
}
func funcB() {
defer func() {
err := recover()
//如果程序出出现了panic错误,可以通过recover恢复过来
if err != nil {
fmt.Println("recover in B")
}
}()
panic("panic in B")
}
func funcC() {
fmt.Println("func C")
}
func main() {
funcA()
funcB()
funcC()
}
/*
func A
recover in B
func C
*/
例子5:
func main() {
defer A1()
defer A2()
panic("panicA1")
}
func A1() {
fmt.Println("A1()")
panic("panicA2")
}
func A2() {
p := recover()
fmt.Println(p)
}
A2中的recover()能捕获 main中的panic,因为整个A2() 是处于defer包装中。。。。
来看个经典的例子,defer_call中调用了panic,defer_call的执行就会被停止,panic之前的所有defer都会被执行,然后defer_call函数返回。对于调用defer_call的main而言,就像是调用了panic,如果没有捕捉该panic,相当于一层一层的panic,程序将会crash。
func main() {
defer_call()
fmt.Println("333 Helloworld")
}
func defer_call() {
defer func() {
fmt.Println("11111")
}()
defer func() {
fmt.Println("22222")
}()
defer func() {
if r := recover(); r != nil {
fmt.Println("Recover from r : ", r)
}
}()
defer func() {
fmt.Println("33333")
}()
fmt.Println("111 Helloworld")
panic("Panic 1!")
fmt.Println("xxxxxxxxxxxxxx")
panic("Panic 2!")
fmt.Println("222 Helloworld")
}
/*
111 Helloworld
33333
Recover from r : Panic 1!
22222
11111
333 Helloworld
*/
总结:
panic触发后,前边的所有defer都会被执行,panic后的代码包括defer 永远不会被执行。。
panic如果没有被消化 ,那么层层向上抛出panic。
不是所有地方的panic都可以恢复的!!!!!!!
// 预先声明的函数 panic 的实现
func gopanic(e interface{}) {
gp := getg()
// 判断在系统栈上还是在用户栈上
// 如果执行在系统或信号栈时,getg() 会返回当前 m 的 g0 或 gsignal
// 因此可以通过 gp.m.curg == gp 来判断所在栈
// 系统栈上的 panic 无法恢复
if gp.m.curg != gp {
print("panic: ") // 打印
printany(e) // 打印
print("\n") // 继续打印,下同
throw("panic on system stack")
}
// 如果正在进行 malloc 时发生 panic 也无法恢复
if gp.m.mallocing != 0 {
print("panic: ")
printany(e)
print("\n")
throw("panic during malloc")
}
// 在禁止抢占时发生 panic 也无法恢复
if gp.m.preemptoff != "" {
print("panic: ")
printany(e)
print("\n")
print("preempt off reason: ")
print(gp.m.preemptoff)
print("\n")
throw("panic during preemptoff")
}
// 在 g 锁在 m 上时发生 panic 也无法恢复
if gp.m.locks != 0 {
print("panic: ")
printany(e)
print("\n")
throw("panic holding locks")
}
...
}
recover失效的条件(永远返回nil)
1:panic时指定的参数为nil (一般panic语句都是这样的panic("XXXXXXXX"))
2:当前协程没有发生panic
3:recover 没有被defer方法直接调用
func IsPanic() bool {
if err := recover(); err != nil {
fmt.Println("Recover success...")
return true
}
return false
}
func UpdateTable() {
// defer中决定提交还是回滚
defer func() {
if IsPanic() {
// Rollback transaction
} else {
// Commit transaction
}
}()
// Database update operation...
}
这个例子正匹配第三条