基础语法
defer 后面必须是函数调用语句,defer a = 1 + 1 或者 defer a++ 都是错误的;
defer 后面的语句在函数执行结束的时候才会被调用,顺序是后入先出;
defer 后面的语句参数是实时解析的;
func main() {
var a = 10
defer fmt.Println("defer:", a)
a++
fmt.Println(a)
}
//
11
defer: 10
常见用法
file 对象或数据库连接等打开后的自动关闭;
mutex 对象加锁后的自动释放;
配合 recover 做异常处理;(出现 panic 时会先按照 defer 后入先出的顺序执行,然后才会向上传递执行 panic,defer 定义的方法如果在 panic 后面则不会被执行到,recover 接收异常后程序仍能继续执行)
func main() {
defer func() { fmt.Println("前") }()
defer func() { fmt.Println("中") }()
fmt.Println("123")
defer func() {
if r := recover(); r != nil {
fmt.Println("Recover from r : ", r)
}
}()
defer func() { fmt.Println("后") }()
panic("触发异常")
defer fmt.Println("end")
}
//
123
后
Recover from r : 触发异常
中
前
避坑
一般要避免在 for 循环中直接使用 defer,如果 for 循环中先分配了资源,再用 defer 回收资源,在一次循环结束时 defer 不会被执行,资源得不到释放,可能带来猝不及防的灾难后果;
问题代码:
for {
time.Sleep(60 * time.Second)
db, _ := sql.Open("mysql", dsn)
defer db.Close()
...
}
可以用匿名函数或者提取函数单独封装 defer 来解决上述问题,修改代码:
// 匿名函数
for {
time.Sleep(60 * time.Second)
func() {
db, _ := sql.Open("mysql", dsn)
defer db.Close()
// ...
}()
}
// 提取函数
for {
time.Sleep(60 * time.Second)
DealDB()
}
func DealDB(){
db, _ := sql.Open("mysql", dsn)
defer db.Close()
// ...
}
map 并发读写操作引起的 panic,defer recover 不会生效;
func main() {
m := make(map[int]int)
go write(m)
for {
fmt.Println(m[0])
}
}
func write(m map[int]int) {
defer func() {
if err := recover(); err != nil {
fmt.Println("write panic:", err)
}
}()
for {
m[0] = 1
}
}
//
0
1
fatal error: concurrent map read and map write
panic 参数为 nil 时,1.20版本中,defer recover 不会处理 nil 异常;
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("nil panic: ", r)
}
}()
fmt.Println(1)
panic(nil)
fmt.Println(2)
}
//
1