Golang defer、panic和recover

一: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...
}

 这个例子正匹配第三条

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值