【问题标题】:Golang defer behaviorGolang 延迟行为
【发布时间】:2014-09-03 09:35:03
【问题描述】:

Effective Go 关于延迟的声明如下:

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

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

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

这个例子让我很困惑。如果在执行 defer 调用时计算参数,那么 for 循环中的 defers 应该打印 5 5 5 5 5,因为当 for 循环结束时将调用 defers,那时 i 将是 5。因此,for 循环的结尾将导致所有调用为 5。

我错过了什么吗?

【问题讨论】:

    标签: go


    【解决方案1】:

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

    如果这个解释是正确的,那么你的语句“因为在 for 循环结束时将调用 defers”是错误的(printf 将在循环之后调用,但 defer 在内部调用),一切都是与其他答案中解释的行为一致。

    【讨论】:

    • 我同意。我认为这些术语:被执行,被调用,被运行是模棱两可的。像您这样解释时更容易理解:“当控制流到达以 defer 开头的行时 - 评估 defer 之后出现的任何函数的参数”。
    【解决方案2】:

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

    延迟函数调用以后进先出的顺序执行周围的函数返回之后。

    这个函数打印“3210”:

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

    评估defer 时的最后一次调用表示i=3,从前到最后表示i=2,依此类推。

    Golang spec:

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


    函数结束时会调用defers

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

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

    【讨论】:

    • 那么,这是否意味着在代码控制流期间,defer 中的所有参数都将被评估并压入堆栈,而只有执行部分被延迟到函数结束?
    • 是的,根据规范:“每次执行“defer”语句时,调用的函数值和参数都会像往常一样进行评估并重新保存,但不会执行实际的函数体。而且您不是唯一对此感到困惑/惊讶的人。 :)
    • 看来defers会在函数(不是for循环)结束时被调用?
    【解决方案3】:

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

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

    是的,参数是一次计算而函数体在另一次运行肯定会让人感到困惑。我被它抓住了。

    【讨论】:

      猜你喜欢
      • 2018-06-24
      • 1970-01-01
      • 1970-01-01
      • 2018-09-22
      • 1970-01-01
      • 2020-07-14
      • 1970-01-01
      • 2014-03-01
      • 1970-01-01
      相关资源
      最近更新 更多