延迟函数defer是Golang的一个语法糖,逻辑上defer是与普通函数适配的。所以在使用defer时,应该对应到一个函数中,而不是一个for循环语句内或一个代码块中,因为这样做是危险和无意义的。
import (
"fmt"
"sync"
)
func main() {
/* 问题一:defer nil函数 */
func(){
var run func() = nil
defer run()
fmt.Println("runs")
}()
// 匿名函数执行结束后,defer函数被执行,因为run()为nil,而出现panic
/* 问题二 在循环中使用defer */
func() {
for {
wg := sync.WaitGroup{}
wg.Add(1)
// do something
defer wg.Done()
}
}()
// defer延迟函数会在该匿名函数结束后运行,而不是每次的for循环结束后。
// 于是这些延迟函数会在每次循环中不停地堆积到延迟调用栈,最终导致不可预知的问题
/* 问题二的解决方案1 */
func() {
for {
wg := sync.WaitGroup{}
wg.Add(1)
// do something
wg.Done()
}
}()
// 这里需要注意,不使用defer,直接在for循环的每一次的末尾进行结束
/* 问题二的解决方案2 */
func() {
for {
func() {
wg := sync.WaitGroup{}
wg.Add(1)
// do something
defer wg.Done()
}()
}
}()
// for循环中嵌套匿名函数,这样延迟函数defer会在每次内部的匿名函数结束后执行
/* 问题三 延迟调用含有闭包的函数 */
{
db := &database{}
defer db.connect()
fmt.Println("query db...")
/* Output:
query db...
connect
*/
} // connect()函数执行结束后,其执行域被保存起来,但内部的闭包不会执行
/* 问题三的解决方案1 */
func() {
db := &database{}
close := db.connect()
defer close()
fmt.Println("query db...")
}()
// 将db.database()返回了一个函数,再对该函数使用defer,就可以在匿名函数结束后实际执行
/* 问题三的解决方案2 */
func() {
db := &database{}
defer db.connect()()
}()
// 这里与之前的解决方案没有本质区别。第一个括号是执行connect函数内部的方法,
// 第二个括号是执行返回的闭包
}
type database struct {}
func (db *database) connect() (disconnect func()) {
fmt.Println("connect")
return func() {
fmt.Println("disconnect")
}
}
/* 问题四:在执行块中使用defer */
func main() {
{
defer func() {
fmt.Println("block: defer runs.")
}()
fmt.Println("block: ends")
}
fmt.Println("main: ends")
}
/* Output:
block: ends
main: ends
block: defer runs
*/
// 延迟是相对于一个函数而非一个代码块
func main() {
func() {
defer func() {
fmt.Println("func: defer runs")
}()
fmt.Println("func: ends")
}()
fmt.Println("main: edns")
}
/* Output:
func: ends
func: defer runs
main: edns
*/
// 延迟函数在匿名函数结束后便执行,而不是等到main函数结束defer才执行
/* 问题五:延迟方法的坑 */
type Car struct {
model string
}
func (c Car) PrintModel() {
fmt.Println(c.model)
}
func (c *Car) PrintModel1() {
fmt.Println(c.model)
}
func main() {
c := Car{model: "DeLorean DMC-12"}
defer c.PrintModel()
//defer c.PrintModel1()
c.model = "Chevrolet Impala"
}
/*
当defer调用的是PrintModel()时,输出结果是:DeLorean DMC-12
当defer调用的是PrintModel1()时,输出结果是:Chevrolet Impala
当以值为接收者的方法被defer修饰时,接收者会在声明时被拷贝,之后的修改都不会影响到这次拷贝的值
而当以指针对象作为接收者的方法被defer修饰时,生成的新的指针变量的指向的地址与原对象c的指向地址相同,因此任何修改都作用于同一个对象中。
*/
将defer置于for循环中,每一次循环的defer函数都会保存在Defers stack中,所以结果是反向打印出来的。
/* 问题六 */
func main() {
for i:=0;i<4;i++ {
defer fmt.Println(i)
}
}
/*Output:
3
2
1
0
*/
下面的例子中,输出的结果是Hello的原因是,defer函数虽然是在所属函数执行完后才会执行,但是m.print()
却是按照代码顺序立即执行出结果了,所以defer保存的就是Hello
,故最后输出的结果是Hello
。
/* 问题七 */
type message struct {
content string
}
func (p *message) set(c string) {
p.content = c
}
func (p *message) print() string {
return p.content
}
func main() {
func() {
m := &message{content: "Hello"}
defer fmt.Print(m.print())
m.set("World")
// deferred func runs
}()
}
/*Output:
Hello
*/
变量i
被初始化之后其值0
会保存在某一个地址内,而循环结束后defer才会执行完毕,而defer准备去执行的时候,变量i
的值是3
,所以结果是打印三次3
。
/* 问题八 */
func main() {
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i)
}()
}
}
/*Output:
3
3
3
*/
为了想实现打印出2 1 0
,有如下解决方案。
如此,go runtime会创建不同的变量i
(i已经作为一个实参传入,那么实际上是会被复制的),并且保存预期正确的值。
func main() {
for i := 0; i < 3; i++ {
defer func(i int) {
fmt.Println(i)
}(i)
}
}
/*Output:
2
1
0
*/
下面的例子,道理类似。将i
赋给一个新的同名变量i
,所以在每次循环时,都会初始化一个同名i
的变量,所以执行三次defer函数时,都不会使用同一个值。
func main() {
for i := 0; i < 3; i++ {
i := i
defer func() {
fmt.Println(i)
}()
}
}
/*Output:
2
1
0
*/
这个例子能得到预期的结果,原因与之前例子打印出Hello
的原因类似,defer后直接使用了打印语句,其实结果已经得到,只是并没有执行打印到控制台。当main函数结束后,defer会把之前得到的结果(结果保存在Defers Stack中)逐一弹出。
func main() {
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}
}
/*Output:
2
1
0
*/
首先需要提及的一个知识点是named result values
,它可以让你将需要返回的结果值当作声明的变量来使用,并且你可以仅仅返回一个赤裸裸的return
。
然后看一下下面的例子,release()
函数返回的结果是nil,因为defer函数的return
是不会影响到正常的返回值。而如果强行想改变,那么需要考虑正常返回的变量和defer函数中返回的变量是同一个地址的变量。那么应用到命名结果值
,在release1()
函数中,defer延迟函数里,err的值被赋值为“error”
,所以在返回时如果err
变量不为空(为空当然会返回nil),就会返回err
的值,如此defer延迟函数里的赋值就生效了。
/* 问题九 */
func release() error {
defer func() error {
return errors.New("error")
}()
return nil
}
func release1() (err error) {
defer func() {
err = errors.New("error")
}()
return nil
}
func main() {
fmt.Println(release())
fmt.Println(release1())
}
/*Output:
<nil>
error
*/
参考文章的示例,有所改动,并且内容并非完全翻译,都是笔者自己的话,所以不作为译文。
参考文章:
5 Gotchas of Defer in Go — Part I
5 More Gotchas of Defer in Go — Part II