Skip to content

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

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

测试如下:

go
func main() {
	for i := 0; i < 3; i++ {
		fmt.Println("before:", i)
		defer fmt.Println("defer:", i)
		fmt.Println("after:", i)
	}
}

输出为

bash
before: 0
after: 0
before: 1
after: 1
before: 2
after: 2
defer: 2
defer: 1
defer: 0

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

解决方法:

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

go
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循环中。使用匿名函数依然不可行,如下:

go
// 所有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)
	}
}

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

go
// 所有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)
	}
}

完毕。