Hello,我是普通Gopher,00后男孩,极致的共享主义者,想要成为一个终身学习者。专注于做最通俗易懂的计算机基础知识类公众号。每天推送Golang技术干货,内容起于K8S而不止于K8S,涉及Docker、微服务、DevOps、数据库、虚拟化等云计算内容及SRE经验总结
=======================
初次见面,我为你准备了100G学习大礼包:
1、《百余本最新计算机电子图书》
2、《30G Golang学习视频》
3、《20G Java学习视频》
4、《90G Liunx高级学习视频》
5、《10G 算法(含蓝桥杯真题)学习视频》
6、《英语四级,周杰伦歌曲免费送!》
路过麻烦动动小手,点个关注,持续更新技术文章与资料!
defer
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制)
简单的demo
package main
import "fmt"
func main() {
res := sum(10, 20)
fmt.Println("number4 res=", res)
}
func sum(n1 int, n2 int) int {
// 函数执行完毕后,及时的释放资源
// 先进后出
defer fmt.Println("number1 n1 = ", n1)
defer fmt.Println("number2 n2 = ", n2)
res := n1 + n2
fmt.Println("number3 res = ", res) // 最先执行
return res
}
输出:
number3 res = 30
number2 n2 = 20
number1 n1 = 10
number4 res= 30
-
当
go
执行到一个defer
时,不会立即执行defer
后的语句,而是将defer
后的语句压入到一个"栈"(比喻)中, 然后继续执行函数下一个语句。 -
当函数执行完毕后,在从
defer
栈中,依次从栈顶取出语句执行(注:遵守栈先入后出的机制) -
在defer将语句放入到栈时,也会将相关的值拷贝同时入栈
package main
import "fmt"
/*
输出:
number3 res = 30
number2 n2 = 20
number1 n1 = 10
number4 res= 30
*/
func main() {
res := sum(10, 20)
fmt.Println("number4 res=", res)
}
func sum(n1 int, n2 int) int {
// 函数执行完毕后,及时的释放资源
// 先进后出
defer fmt.Println("number1 n1 = ", n1)
defer fmt.Println("number2 n2 = ", n2)
// 增加一段
n1++
n2++
res := n1 + n2
fmt.Println("number3 res = ", res) // 最先执行
return res
}
输出:
number3 res = 32 // 输出结果为32
number2 n2 = 20 // 栈中的数值仍是存入前的数值
number1 n1 = 10
number4 res= 32
使用defer+recover来处理错误
package main
import (
"fmt"
"time"
)
func main() {
test()
for {
fmt.Println("main()下面的代码...")
time.Sleep(time.Second)
}
}
func test() {
defer func() {
err := recover() // recover()内置函数,可以捕获到异常
if err != nil { // 捕获到异常
fmt.Println("err=", err)
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("res=", res)
}
输出:
err= runtime error: integer divide by zero
main()下面的代码...
defer在Go中的数据结构
type _defer struct {
siz int32
started bool
sp uintptr
pc uintptr
fn *funcval
_panic *_panic
link *_defer
}
runtime._defer
结构体是延迟调用链表上的一个元素,所有的结构体都会通过 link
字段串联成链表。
- siz 是参数和结果的内存大小;
- sp 和 pc 分别代表栈指针和调用方的程序计数器;
- fn 是 defer 关键字中传入的函数;
- _panic 是触发延迟调用的结构体,可能为空;
除了上述的这些字段之外,runtime._defer
中还包含一些垃圾回收机制使用的字段,这里为了减少理解的成本就都省去了
小结
defer
关键字的实现主要依靠编译器和运行时的协作
编译期;
将 defer
关键字被转换runtime.deferproc
;
在调用defer
关键字的函数返回之前插入runtime.deferreturn;
运行时:
runtime.deferproc
会将一个新的 runtime._defer
结构体追加到当前Goroutine
的链表头;
runtime.deferreturn
会从 Goroutine
的链表中取出runtime._defer
结构并依次执行;
- 后调用的
defer
函数会先执行:- 后调用的
defer
函数会被追加到Goroutine _defer
链表的最前面; - 运行
runtime._defer
时是从前到后依次执行;
- 后调用的
- 函数的参数会被预先计算;
- 调用
runtime.deferproc
函数创建新的延迟调用时就会立刻拷贝函数的参数,函数的参数不会等到真正执行时计算;
- 调用
更多关于Golang defer的编译过程与运行过程可以参考:
参考链接:https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-defer/#533-