Golang延迟行为

问题描述 投票:45回答:3

Effective Go声明以下有关延期:

延迟函数的参数(如果函数是方法,包括接收器)在延迟执行时计算,而不是在调用执行时计算。除了避免担心变量在函数执行时更改值,这意味着单个延迟调用站点可以推迟多个函数执行。这是一个愚蠢的例子。

for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}

延迟函数以LIFO顺序执行,因此该代码将在函数返回时打印4 3 2 1 0

这个例子让我很困惑。如果在执行延迟调用时评估参数,那么for循环中的延迟应该打印5 5 5 5 5,因为当for循环结束时将调用defers,并且那时i将为5.在for的结尾处评估延迟因此,循环将导致所有呼叫的5。

我在这里错过了什么吗?

go
3个回答
42
投票

这似乎是连贯的(另见“Defer, Panic, and Recover”)

在周围函数返回后,延迟函数调用以Last In First Out顺序执行。

此功能打印“3210”:

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}

评估defer时的最后一次调用意味着i=3,前一个调用意味着i=2,依此类推。

Golang spec

每次执行“defer”语句时,调用的函数值和参数将照常计算并重新保存,但不执行实际的函数体。


当func结束时,将调用defers

是的,但是他们的参数在循环运行之前进行了评估。

当你使用封闭(How golang's “defer” capture closure's parameter?)时,你在“function literal”中有一个更棘手的延迟案例,详见“Why add “()” after closure body in Golang?”。


13
投票

更进一步,spec还明确表示在defer语句运行时评估参数,而不是在实际调用延迟函数时的返回/恐慌时间:

每次执行“延迟”语句时,将像往常一样评估调用的函数值和参数,并重新保存,但不执行实际的函数体。

是的,一次评估参数并且函数体在另一个上运行肯定会令人困惑。我被它抓住了。


4
投票

我认为你的困惑是关于“延迟执行”和“呼叫执行”这两个词的含义。我相信,“延迟执行”是当控制流量达到以defer开始的线时,即这在循环内发生了五次。相反,“调用执行”是在执行fmt.Printf("%d ", i)时,即当周围函数返回时。

如果这种解释是正确的,那么你的语句“因为当for循环结束时将会调用defers”是错误的(printf将在循环之后调用,但defer在内部调用),并且所有内容都符合在其他答案。

© www.soinside.com 2019 - 2024. All rights reserved.