[Golang] Possible resource leak, 'defer' is called in the 'for' loop

本文共375字。
Copyright: 知识共享署名 非商业性使用 相同方式共享 4.0 国际许可协议 | CC BY-NC-SA 4.0

今天被goland警告在golang的for循环中使用defer可能会导致资源泄漏,主要是因为defer的作用域是一个函数,不是一个语句块。

测试如下:

1
2
3
4
5
6
7
func main() {
for i := 0; i < 3; i++ {
fmt.Println("before:", i)
defer fmt.Println("defer:", i)
fmt.Println("after:", i)
}
}

输出为

1
2
3
4
5
6
7
8
9
before: 0
after: 0
before: 1
after: 1
before: 2
after: 2
defer: 2
defer: 1
defer: 0

defer并不会在一次for循环执行完成后执行defer,而是会在整个函数体执行完成后执行defer。且defer的执行顺序为先进后出,类似堆栈。

解决方法:

将defer放在函数中执行。如下:

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
for i := 0; i < 3; i++ {
loopBody(i)
}
}

// defer会在loopBody(i int)执行完毕后再执行
func loopBody(i int) {
fmt.Println("before:", i)
defer fmt.Println("defer:", i)
fmt.Println("after:", i)
}

注意,要点在于不能使defer关键字出现在for循环中。使用匿名函数依然不可行,如下:

1
2
3
4
5
6
7
8
// 所有defer会在main()执行完毕后再执行
func main() {
for i := 0; i < 3; i++ {
fmt.Println("before:", i)
defer func(i int) {fmt.Println("defer:", i)}(i)
fmt.Println("after:", i)
}
}

匿名函数更不可以写成如下方式:

1
2
3
4
5
6
7
8
// 所有defer会在main()执行完毕后再执行,且均输出defer: 3
func main() {
for i := 0; i < 3; i++ {
fmt.Println("before:", i)
defer func() {fmt.Println("defer:", i)}() // 注意此处函数不接受参数
fmt.Println("after:", i)
}
}

完毕。