一:defer

延迟处理,倒序执行

能够触发defer的是遇见return(或函数体到末尾)和遇见panic

func f1() int {x := 5defer func() {x++}()return x
}
func f2() (x int) {defer func() {x++}()return 5
}
func f3() (y int) {x := 5defer func() {x++}()return x
}
//defer中的x是局部变量 所以……
func f4() (x int) {defer func(x int) { x++    //更改的是局部变量}(x)   //传进去的是0return 5
}
func main() {fmt.Println(f1())  //5fmt.Println(f2())  //6fmt.Println(f3())  //5fmt.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 + bfmt.Println(index, a, b, ret)return ret
}func main() {x := 1y := 2defer calc("AA", x, calc("A", x, y))x = 10defer calc("BB", x, calc("B", x, y))y = 20
}
//A 1 2 3
//B 10 2 12
//BB 10 12 22
//AA 1 3 4func DeferFunc4() (t int) {defer func(i int) {fmt.Println(i)fmt.Println(t)}(t)t = 1return 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 mainimport ("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...
}

这个例子正匹配第三条

Golang defer、panic和recover相关推荐

  1. Golang的Panic和Recover

    什么是 panic? 在 Go 语言中,程序中一般是使用错误来处理异常情况.对于程序中出现的大部分异常情况,错误就已经够用了. 但在有些情况,当程序发生异常时,无法继续运行.在这种情况下,我们会使用  ...

  2. Golang中panic与recover的实现原理

    今天我们讲讲golang中panic异常,以及recover对异常的捕获,由于panic.recover.defer之间非常亲密,所以今天就放在一起讲解,这里会涉及到一些defer的知识,有兴趣可以看 ...

  3. Go语言中使用panic和recover简化错误处理

    随着使用golang越来越频繁,发现golang有一个地方非常不方便,就是在错误处理方面.先来看看golang中通常的错误处理方法: 通常的error处理 1 2 3 4 5 6 7 8 9 10 1 ...

  4. panic和recover的使用规则

    转自:https://www.cnblogs.com/vikings-blog/p/7109519.html 转自个人博客 chinazt.cc 在上一节中,我们介绍了defer的使用. 这一节中,我 ...

  5. 在golang中defer、panic与recover的作用

    package mainimport "fmt"func main() {var s strings = "panic"fmt.Printf("a的初 ...

  6. golang中的panic和recover

    golang中的panic需要recover捕获,不然程序就会挂掉 package mainimport "fmt"func main() {f1()fmt.Println(&qu ...

  7. go defer,panic,recover详解 go 的异常处理

    golang中defer,panic,recover是很常用的三个特性,三者一起使用可以充当其他语言中try-catch-的角色,而defer本身又像其他语言的析构函数 defer defer后边会接 ...

  8. Golang 错误捕获 Panic 与 Recover

    Golang 错误捕获 Panic 与 Recover,我抓住你了,Error Golang轻松学习 文章目录 Golang 错误捕获 Panic 与 Recover,我抓住你了,Error 一.Go ...

  9. go的异常处理,defer,panic,recover

    比较经典的一个例子,里面包含defer,panic,recover 例子代码: package main import "fmt" func main(){ defer func( ...

  10. Golang中的panic和recover(捕获异常)

    Golang中的panic和recover(捕获异常) 参考文章: (1)Golang中的panic和recover(捕获异常) (2)https://www.cnblogs.com/zhzhlong ...

最新文章

  1. PHP面试中常见的字符串与文件操作题目
  2. 技术系列课回顾 | 网易云信线上万人连麦技术大揭秘
  3. matlab练习程序(三阶张量T-QR分解)
  4. 嵌套函数,匿名函数,高阶函数
  5. 在dialog中使用EditText键盘弹不起来的解决方法
  6. 分布式锁 基于Redis
  7. android和java中常见 Exception
  8. Hadoop上传文件到HDFS失败
  9. php redis 扩展安装
  10. 数据存储与访问(SharedPreferencesDemo)
  11. Linux源码安装pgadmin4,如何在CentOS 7中安装pgAdmin4(yum 安装方法)
  12. matlab运行后没反映,matlab运行这个程序没有反应,也不报错,是什么原因?
  13. caffe的Leveldb格式数据一(官方介绍)
  14. Dichotomy(递+非递)
  15. Elastic:使用 ElastAlert 发送 Slack 通知
  16. Crontab中的除号(slash)到底怎么用?
  17. Python篇:用python画xy散点图
  18. Oracle官方JDBC jar包下载
  19. FIR 线性相位系统 最小相位系统 滤波器延迟
  20. MFC使用CFile类进行输入输出到文本文件

热门文章

  1. 双目视觉(1)---立体匹配介绍
  2. invalid suffix on literal; C++11 requires a space between literal and string macro [-Wliteral-suffix
  3. 防火墙阻止了IE服务器未响应,ie防火墙如何禁用
  4. 龙芯cpu 3A3000搭建electron运行环境
  5. 01、3dB双分支定向耦合器
  6. 技术面试时该反问面试官什么问题?
  7. 6个Web前端值得收藏很实用的菜单模板(下)
  8. vuejs搭建的项目对于ie浏览器的处理
  9. 洛谷 P3403 跳楼机 题解
  10. 数据挖掘-贡献度分析