Go-关键字defer、panic、recover详解_go panic defer

for i:=1;i<5;i++{
	defer fmt.Println("defer",i)
}
fmt.Println("moreDefer() finish...")

}


**结果**



> 
> moreDefer() finish...  
>  defer 4  
>  defer 3  
>  defer 2  
>  defer 1
> 
> 
> 


栈的顺序调用,先入后出。


### 传参问题


**代码**



func deferPara() {
i := 0
defer fmt.Println(“defer”,i,“in deferPara()”)
i++
fmt.Println("deferPara finish…,i is ", i)
}


或许你认为结果是这样的:



> 
> deferPara finish...,i is  1  
>  defer 1 in deferPara()
> 
> 
> 


因为defer在函数结束前运行嘛,但事实上结果是这样的:



> 
> 
> ```
> deferPara finish...,i is  1
> defer 0 in deferPara()
> ```
> 
> 


defer会在到达所在行时,就将变量复制一份传过去。想到的解决方案如下:


* 引用类型就没有问题了
* 如果参数是值类型,你的defer不修改参数,你可以传地址。
* 如果参数是值类型,你也可以将defer放在函数/方法不修改参数后。
* 如果参数是值类型,你可以使用匿名函数,函数体内再用参数。



func paraFix() {
i := 0
defer fmt.Println(“send addr:defer”,&i,“in paraFix()”)
defer func() {fmt.Println(“no name func: defer”,i,“in paraFix()”)}()
i++
defer fmt.Println(“put defer later:defer”,i,“in deferPara()”)
}


### 源代码


**结构体**


 src->runtime->runtime2.go



type _defer struct {
siz int32 // 包含参数和结果
started bool // 是否开始
heap bool // 是否分配在堆上
openDefer bool // 是否开放编码
sp uintptr // 栈指针
pc uintptr // 程序计数器
fn *funcval // 开放编码时可为nil
_panic *_panic // 正在运行的defer的panic
link *_defer // _defer指针
fd unsafe.Pointer // 预分配的函数数据
varp uintptr
framepc uintptr
}


\_defer是一个单链表(链栈),采用头插的方式,取的时候先取头的。


**编译**


src->cmd->compoile->internal->gc->ssa.go stmt方法的一个case



case ODEFER:
if Debug_defer > 0 {
var defertype string
if s.hasOpenDefers {
defertype = “open-coded”
} else if n.Esc == EscNever {
defertype = “stack-allocated”
} else {
defertype = “heap-allocated”
}
Warnl(n.Pos, “%s defer”, defertype)
}
if s.hasOpenDefers {
s.openDeferRecord(n.Left)
} else {
d := callDefer
if n.Esc == EscNever {
d = callDeferStack
}
s.callResult(n.Left, d)
}


有些defer将在栈上分配,有些在堆上分配。首先是开放编码进行优化,其次是栈,最后是堆,分配到栈上可以节约内存分配带来的额外开销。


## panic


### 执行defer


当panic异常发生时,程序会中断运行,并立即执行在该goroutine中被延迟的函数(defer机制)。随后,程序崩溃并输出日志信息。



func panicDefer() {
fmt.Println(“code before panic”)
defer fmt.Println(“defer in panicDefer”)
panic(“something wrong in panic Defer”)
fmt.Println(“code after panic”)
}


**结果** 



> 
> code before panic  
>  defer in panicDefer  
>  panic: something wrong in panic Defer
> 
> 
> goroutine 1 [running]:  
>  main.panicDefer()  
>          E:/Workspace/Go\_workspace/learn\_go/src/defer\_panic\_recover/main/main.go:42 +0x10a  
>  main.main()  
>          E:/Workspace/Go\_workspace/learn\_go/src/defer\_panic\_recover/main/main.go:62 +0x27  
>  exit status 2
> 
> 
> 


利用defer就可以实现有panic时也能进行资源释放等。


### 跨协程问题


`panic` 只会触发当前 goroutine 的 `defer`



func panicGoroutine() {
defer println(“defer in main”)
go func() {
defer println(“defer in goroutine”)
panic(“something wrong…”)
}()

time.Sleep(time.Second)

}


**结果:** 



> 
> defer in goroutine  
>  panic: something wrong...
> 
> 
> goroutine 6 [running]:  
>  main.panicGoroutine.func1()  
>          E:/Workspace/Go\_workspace/learn\_go/src/defer\_panic\_recover/main/main.go:50 +0x78  
>  created by main.panicGoroutine  
>          E:/Workspace/Go\_workspace/learn\_go/src/defer\_panic\_recover/main/main.go:48 +0x78  
>  exit status 2
> 
> 
> 


协程外面的defer执行不了


### 源代码


**结构体**


src->runtime->runtime2.go



type _panic struct {
argp unsafe.Pointer // 指向defer栈的函数指针
arg interface{} // panic参数
link *_panic // 先前panic的指针
pc uintptr // 运行时,此panic被绕过时返回到哪
sp unsafe.Pointer // 运行时,此panic被绕过时返回到哪
recovered bool // 是否此panic结束
aborted bool // 这个panic被终止
goexit bool
}


**崩溃** 


src->runtime->panic.go



可以查看gopanic、fatalpanic两个函数,代码过多,不黏贴了,有兴趣可以看看。下面是fatalpanic的部分代码:



systemstack(func() {
	exit(2)
})

这就能看出前面panic时为什么是“exit status 2”了


## recover


### 使用


`recover` 可以中止 `panic` 造成的程序崩溃,**只能在 `defer` 中发挥作用**


**代码**



func recoverDefer() {
//defer println(“defer in main”) // 执行不到
defer func() {
if err := recover();err!=nil{
println(“defer in main”)
println(err)
}
}()
go func() {
defer println(“defer in goroutine”)
panic(“something wrong…”)
}()
panic(“something wrong in recoverDefer()”)
time.Sleep(time.Second)
}


**结果**



> 
> defer in main  
>  (0xf4d940,0xf85bc8)  
>  defer in goroutine
> 
> 
> 


### 源代码


src->runtime->panic.go



func gorecover(argp uintptr) interface{} {
gp := getg()
p := gp._panic
if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {
p.recovered = true
return p.arg
}
return nil
}


可以看到,p!=nil的话才行,也就是说你在panic前使用recover,没在defer中,那么返回的是nil,也就是recover失效了。如果不是nil,在gopanic中会进行处理。


## 总结


### defer


* **调用时机**:函数或方法在**返回或结束前执行**
* **多次调用**:先写的后调用,**栈的顺序**
* **传参问题**:值类型时,**参数不改变后使用defer、defer的函数不修改则传地址、匿名函数函数体中使用**


优点:


* panic后执行defer,**防止异常时忘记释放资源**
* 函数复杂分支返回,写一次即可,**简洁,复用性好**


### panic


* panic后执行**本协程**的defer
* **跨协程问题**使用**recover**解决


### recover


* **终止panic造成的崩溃**
* 在**defer中使用时才有效**


## 全部源代码



package main

import (
“fmt”
“time”
)

//------调用时机:所在函数结束或返回前------
func callTime() {
{
defer fmt.Println(“defer in callTime()”)
fmt.Println(“code block finish…”)
}
fmt.Println(“callTime() finish…”)
}
//-----多个defer的顺序------
func moreDefer() {
for i:=1;i<5;i++{
defer fmt.Println(“defer”,i)
}
fmt.Println(“moreDefer() finish…”)
}
//-----defer传参问题-----
func deferPara() {
i := 0
defer fmt.Println(“defer”,i,“in deferPara()”)
i++
fmt.Println("deferPara finish…,i is ", i)
}
//-----defer传参修复方案--------
func paraFix() {
i := 0
defer fmt.Println(“send addr:defer”,&i,“in paraFix()”)
defer func() {fmt.Println(“no name func: defer”,i,“in paraFix()”)}()
i++
defer fmt.Println(“put defer later:defer”,i,“in deferPara()”)
}
//----panic后执行defer-------
func panicDefer() {
fmt.Println(“code before panic”)
defer fmt.Println(“defer in panicDefer”)
panic(“something wrong in panic Defer”)
fmt.Println(“code after panic”)
}
//----跨协程 defer panic问题-----
func panicGoroutine() {
defer println(“defer in main”)
go func() {
defer println(“defer in goroutine”)
panic(“something wrong…”)
}()

time.Sleep(time.Second)

}
//----------defer中使用recover---------
func recoverDefer() {
//defer println(“defer in main”) // 执行不到
defer func() {
if err := recover();err!=nil{
println(“defer in main”)
println(err)
}
}()
go func() {
defer println(“defer in goroutine”)
panic(“something wrong…”)
}()
panic(“something wrong in recoverDefer()”)
time.Sleep(time.Second)
}

func main() {
//callTime()
//moreDefer()
//deferPara()
//paraFix()
//panicDefer()
//panicGoroutine()
recoverDefer()
}


## 参考


[知乎-在go语言中,为什么使用defer?]( )


go-1.16.3源代码


更多Go相关内容:[Go-Golang学习总结笔记]( )


有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。如果您感觉有所收获,自愿打赏,可选择支付宝18833895206(小于),您的支持是我不断更新的动力。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值