Golang的defer行为

huangapple go评论90阅读模式
英文:

Golang defer behavior

问题

Effective Go中关于defer的说明如下:

延迟函数的参数(包括接收器,如果函数是一个方法)在defer执行时进行求值,而不是在调用执行时进行求值。除了避免函数执行过程中变量值的变化带来的困扰外,这意味着单个延迟调用点可以延迟多个函数的执行。下面是一个愚蠢的例子。

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

延迟函数按照后进先出(LIFO)的顺序执行,因此当函数返回时,这段代码将会打印出4 3 2 1 0

这个例子让我感到困惑。如果参数在defer调用时进行求值,那么for循环中的defer应该打印出5 5 5 5 5,因为defer将在for循环结束时调用,而此时i的值将为5。因此,在for循环结束时求值defer将导致所有调用的结果都为5。

我在这里有什么理解上的错误吗?

英文:

Effective Go
states the following regarding defer:

> The arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes, not when the call executes. Besides avoiding worries about variables changing values as the function executes, this means that a single deferred call site can defer multiple function executions. Here's a silly example.

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

> Deferred functions are executed in LIFO order, so this code will cause 4 3 2 1 0 to be printed when the function returns.

This example confuses me. If parameters are evaluated when the defer call is executed, then the defers in the for loop should print 5 5 5 5 5 since the defers will be called when the for loop ends, and at that time i would be 5. Evaluating defers at the end of the for loop will thus result in 5 for all calls.

Am I missing something here?

答案1

得分: 60

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

延迟函数调用按照后进先出的顺序在包围函数返回之后执行。

这个函数会打印“3210”:

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

defer被评估时,最后一次调用意味着i=3,倒数第二次调用意味着i=2,依此类推。

Golang规范

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


defer会在函数结束时被调用

是的,但它们的参数在循环运行时会被评估。

在“How golang's “defer” capture closure's parameter?”中,你会遇到一个更棘手的defer情况,当它与闭包(函数字面量)一起使用时,详细信息请参见“Why add “()” after closure body in Golang?”。

英文:

That seems coherent (see also "Defer, Panic, and Recover")

Deferred function calls are executed in Last In First Out order after the surrounding function returns.

This function prints "3210":

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

The last call when the defer is evaluated means i=3, the previous to last means i=2 and so on.

Golang spec:

> Each time the "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function body is not executed.


> the defers will be called when func ends

yes, but their arguments are evaluated before, while the loop is running.

You have a trickier defer case in "How golang's “defer” capture closure's parameter?" when used with closure (function literal), as detailed in "Why add “()” after closure body in Golang?".

答案2

得分: 14

稍微往下看,规范还明确指出,在defer语句执行时,参数会被按照通常的方式进行评估并保存,但实际的函数体并不会被执行,而不是在返回/恐慌时调用延迟函数时才执行。

是的,参数在某个时间点进行评估,而函数体在另一个时间点运行,这确实可能令人困惑。我也曾被这个问题困扰过。

英文:

A little further down, the spec also explicitly says that parameters are evaluated at the time the defer statement runs, not at return/panic time when the deferred function is actually called:

> Each time the "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function body is not executed.

And yes, it can definitely be confusing that the parameters are evaluated at one time and the function body runs at another. I've been caught by it.

答案3

得分: 5

我认为你对于"the defer executes"和"the call executes"这两个短语的含义感到困惑。我认为,"the defer executes"是指当控制流达到以defer开头的行时执行,也就是在循环内部会执行五次。相反,"the call executes"是指当执行fmt.Printf("%d ", i)时执行,也就是当包围函数返回时执行。

如果我的解释正确,那么你的说法"since the defers will be called when the for loop ends"是错误的(printf会在循环之后调用,但是defer是在循环内部调用),而且一切都与其他答案中解释的行为一致。

英文:

I think your confusion is about what the phrases "the defer executes" and "the call executes" mean. I believe, "the defer executes" is when the flow of control reaches the line starting with defer, i.e. this happens five times inside the loop. In contrast, "the call executes" is when the fmt.Printf(&quot;%d &quot;, i) is executed, i.e. when the surrounding function returns.

If this interpretation is correct, the your statement "since the defers will be called when the for loop ends" is wrong (printf will be called after the loop, but defer is called inside), and everything is consistent with the behaviour explained in the other answers.

答案4

得分: 0

Defer提供了在函数执行后执行代码的机会。例如,在函数终止后关闭资源。为了实现这一点,defer函数会捕获函数执行的变量和状态,然后将defer插入到一个GO队列中。当函数终止时,GO会判断是否有任何队列不为空,并执行defer函数。

英文:

Defer provides the opportunity to execute code after function is executed. For example, closing a resource after the function terminates. To accomplish this the defer function does a capture of the function execution (captures the variables and state of the function), then the defer is inserted into a GO queue. When the function terminates, GO will determine if any queue is not empty and executes the defer function.

huangapple
  • 本文由 发表于 2014年7月13日 14:20:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/24720097.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定