📚 全文字数 : 3k
⏳ 阅读时长 : 5min
📢 关键词 : defer、资源释放、底层实现
1:defer是什么
defer是Go语言的关键字,一般用于资源的释放和异常的捕捉(比如:文件打开、加锁、数据库连接、异常捕获),defer语句后将其后面跟随的语句进行延迟处理,就是说在函数执行完毕后再执行调用,也就是return的ret指令之前。
1.1 资源释放
资源的释放在代码中有很多场景,比如打开文件描述符资源后,需要进行file.close得到释放,在打开文件后就加上defer,避免在后续因为err导致的return退出忘记释放,文件资源。
go
func openFile() {
file, err := os.Open("txt")
if err != nil {
return
}
defer file.Close() //合理位置
}
常见的加锁场景,业务代码中忘记释放锁,那么会导致资源得不到释放,造成死锁,但是defer就很好解决了这个问题,不管业务逻辑怎么处理,最终还是会释放锁。
arduino
func lockScene() {
var mutex sync.Mutex
mutex.Lock()
defer mutex.Unlock()
//业务代码...
}
1.2 捕获异常
Go 语言中 recover 关键字主要用于捕获异常,让程序回到正常状态。recover 可以中止 panic 造成的程序崩溃。它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用
go
func demo() {
defer func() {
if err := recover(); err !=nil{
fmt.Println(string(Stack()))
}
}()
panic("unknown")
}
2:defer语法
defer语法相对简单,直接在普通函数之前加一个defer关键字
scss
defer demoFunc(args)
虽然defer语法简单,但是当有多个defer注册时,会以逆序执行(类似栈:先进后出),举个栗子。
go
func f1() {
defer fmt.Println("defer1")
defer fmt.Println("defer2")
fmt.Println("start")
fmt.Println("end")
return
}
这段代码的字符串输出结果是:start、end、defer2、defer1。首先输出的defer字符串在正常的start、end后输出可以很好理解(defer在函数返回前执行),字符串defer2在defer1前输出(逆序执行)。
3:defer与return
Go语言中函数的 return 语句并不是原子级的,实际的执行过程为为设置返回值--->ret指令,defer 语句是在返回前执行,所以返回过程是:「设置返回值--->执行defer--->ret」
4:defer底层实现
要了解defer的实现,先看下defer的底层数据结构和各个参数表示的意义(src/runtime/runtime2.go)
go
type _defer struct {
siz int32 // 参数和返回值的内存大小
started bool
heap bool //是否分配在堆上面
openDefer bool // 是否经过开放编码优化
sp uintptr // sp 计数器值,栈指针
pc uintptr // pc 计数器值,程序计数器
fn *funcval // defer 传入的函数地址,也就是延后执行的函数
_panic *_panic // defer 的 panic 结构体
link *_defer // 同一个协程里面的defer 延迟函数,会通过该指针连接在一起
}
defer怎么实现延迟的
通过资料了解到,defer在代码中的位置在编译后会有两部分内容: 1:deferproc负责把要执行的函数保存起来,我们称之为defer注册 2:deferreturn是在defer注册完成(deferproc)后,程序执行后续业务代码,直到通过deferreturn执行注册的defer函数
为啥是逆序执行
defer结构有个link指针,是指向的一个defer单链表的头,每次咱们声明一个defer的时候,就会将该defer的数据插入到这个单链表头部的位置,取defer进行执行的时候,是从单链表的头开始去取的,这就是defer先进后出的原因。 底层代码在src/runtime/panic.go,核心代码做了说明
go
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
gp := getg() //获取goroutine结构
if gp.m.curg != gp {
// go code on the system stack can't defer
throw("defer on system stack")
}
...
d := newdefer(siz) //新建一个defer结构
if d._panic != nil {
throw("deferproc: d.panic != nil after newdefer")
}
d.link = gp._defer // 新建defer的link指针指向g的defer
gp._defer = d // 新建defer放到g的defer位置,完成插入链表表头操作
d.fn = fn
d.pc = callerpc
d.sp = sp
...
}
如图:先声明defer fun1()、再声明 defer fun2(),fun2()在单链表链表头部。
总结
1:defer关键字后面必须是函数,也叫延迟函数
2:defer是逆序执行(后进先出),延迟函数中的参数在defer声明的时候已经确定了
3:在函数return之前执行延迟函数
文末安利一波:
欢迎朋友们关注我的同名公众号📢📢:【小许code】,等你哦!🤣🤣
欢迎点赞 👍、收藏 💙、关注 💡 三连支持一下~🎈🎈知道的越多,不知道的也越多,我是小许,下期见~🙇💻