Go – 延迟函数的不一致评估

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

Go - Inconsistent Evaluation of Deferred Functions

问题

我正在尝试使用Go语言,并且在使用延迟函数时遇到了一些意外的行为。请考虑以下程序,它通过给定的数量增加一个全局变量。

package main

import "fmt"

var z = 1

func main() {

	defer increaseZ(10)
	defer fmt.Println("z =", increaseZ(20), "Deferred Value 1")
	defer fmt.Println("z =", increaseZ(30), "Deferred Value 2")

	fmt.Println("z =", z, "Main Value")
}

func increaseZ(y int) int {
	z += y
	println("z =", z, "Inside Increase Function")
	return z
}

当在Go Playground中运行时,输出结果如下:

z = 21 Inside Increase Function
z = 51 Inside Increase Function
z = 61 Inside Increase Function
z = 51 Main Value
z = 51 Deferred Value 2
z = 21 Deferred Value 1

如果我改变延迟函数的顺序,会产生另一种效果:

defer fmt.Println("z =", increaseZ(20), "Deferred Value 1")
defer fmt.Println("z =", increaseZ(30), "Deferred Value 2")
defer increaseZ(10)

输出结果为:

z = 21 Inside Increase Function
z = 51 Inside Increase Function
z = 51 Main Value
z = 61 Inside Increase Function
z = 51 Deferred Value 2
z = 21 Deferred Value 1

Go的文档中提到:

延迟调用的参数会被立即求值,但是函数调用会在包围函数返回之后执行。

因此,参数被求值可能解释了为什么返回的Main Value是51而不是61,因为fmt.Println语句将increaseZ作为参数,但是defer increaseZ(10)直到主函数返回后才会被调用。

然而,这并不能解释为什么在第一个示例中,increaseZ(10)在主函数完成之前输出,在第二个示例中在主函数完成之后输出。

如果有人能帮助我理解这里发生了什么,我将不胜感激,因为这看起来是一个容易导致难以诊断的错误的问题。

英文:

I am experimenting with Go and am seeing some unexpected behaviour with deferred functions. Consider the following program that increments a global variable by a given amount.

package main

import "fmt"

var z = 1

func main() {

	defer increaseZ(10)
	defer fmt.Println("z =", increaseZ(20), "Deferred Value 1")
	defer fmt.Println("z =", increaseZ(30), "Deferred Value 2")

	fmt.Println("z =", z, "Main Value")
}

func increaseZ(y int) int {
	z += y
	println("z =", z, "Inside Increase Function")
	return z
}

When run in the go playground, this outputs:

z = 21 Inside Increase Function
z = 51 Inside Increase Function
z = 61 Inside Increase Function
z = 51 Main Value
z = 51 Deferred Value 2
z = 21 Deferred Value 1

If I switch the order of the deferred functions, it has another effect:

defer fmt.Println("z =", increaseZ(20), "Deferred Value 1")
defer fmt.Println("z =", increaseZ(30), "Deferred Value 2")
defer increaseZ(10)

Outputs:

z = 21 Inside Increase Function
z = 51 Inside Increase Function
z = 51 Main Value
z = 61 Inside Increase Function
z = 51 Deferred Value 2
z = 21 Deferred Value 1

The Go documentation states:

> The deferred call's arguments are evaluated immediately, but the
> function call is not executed until the surrounding function returns.

So arguments being evaluated, may explain why the Main Value returned is 51 and not 61, since the fmt.Println statements are taking increaseZ as an argument, but defer increaseZ(10) would not be called until after the main function returns.

However, this does not explain why in the first example the increaseZ(10) is outputting before main has completed, and after main has completed in the second example.

I would be grateful if anyone could help me understand what is happening here, since this looks like fertile ground for difficult to diagnose bugs further down the line.

答案1

得分: 2

你在打印目标上存在不一致。

stdout: fmt.Println

stderr: println

写入相同的打印目标。

package main

import "fmt"

var z = 1

func main() {

    defer increaseZ(10)
    defer fmt.Println("z =", increaseZ(20), "Deferred Value 1")
    defer fmt.Println("z =", increaseZ(30), "Deferred Value 2")

    fmt.Println("z =", z, "Main Value")
}

func increaseZ(y int) int {
    z += y
    fmt.Println("z =", z, "Inside Increase Function")
    return z
}

输出:

z = 21 Inside Increase Function
z = 51 Inside Increase Function
z = 51 Main Value
z = 51 Deferred Value 2
z = 21 Deferred Value 1
z = 61 Inside Increase Function

或者,

package main

import (
	"fmt"
	"os"
)

var z = 1

func main() {

	defer increaseZ(10)
	defer fmt.Fprintln(os.Stderr, "z =", increaseZ(20), "Deferred Value 1")
	defer fmt.Fprintln(os.Stderr, "z =", increaseZ(30), "Deferred Value 2")

	fmt.Fprintln(os.Stderr, "z =", z, "Main Value")
}

func increaseZ(y int) int {
	z += y
	println("z =", z, "Inside Increase Function")
	return z
}

输出:

z = 21 Inside Increase Function
z = 51 Inside Increase Function
z = 51 Main Value
z = 51 Deferred Value 2
z = 21 Deferred Value 1
z = 61 Inside Increase Function
英文:

You are being inconsistent in your print destination.

stdout: fmt.Println

stderr: println

Write to the same print destination.

package main

import "fmt"

var z = 1

func main() {

    defer increaseZ(10)
    defer fmt.Println("z =", increaseZ(20), "Deferred Value 1")
    defer fmt.Println("z =", increaseZ(30), "Deferred Value 2")

    fmt.Println("z =", z, "Main Value")
}

func increaseZ(y int) int {
    z += y
    fmt.Println("z =", z, "Inside Increase Function")
    return z
}

Output:

z = 21 Inside Increase Function
z = 51 Inside Increase Function
z = 51 Main Value
z = 51 Deferred Value 2
z = 21 Deferred Value 1
z = 61 Inside Increase Function

or,

package main

import (
	"fmt"
	"os"
)

var z = 1

func main() {

	defer increaseZ(10)
	defer fmt.Fprintln(os.Stderr, "z =", increaseZ(20), "Deferred Value 1")
	defer fmt.Fprintln(os.Stderr, "z =", increaseZ(30), "Deferred Value 2")

	fmt.Fprintln(os.Stderr, "z =", z, "Main Value")
}

func increaseZ(y int) int {
	z += y
	println("z =", z, "Inside Increase Function")
	return z
}

Output:

z = 21 Inside Increase Function
z = 51 Inside Increase Function
z = 51 Main Value
z = 51 Deferred Value 2
z = 21 Deferred Value 1
z = 61 Inside Increase Function

答案2

得分: 2

这不是关于延迟评估的问题,而是关于打印输出的问题。println函数只是为了完整性而记录,但不能保证一直存在于语言中。此外,在Playground中,Stdout和Stderr被设计为合并为一个流。
如果你在每个地方都使用fmt.Println(...),或者明确地定义fmt.Fprintln(os.Stdout, ...),那么事情将按预期工作。

英文:

It's not about deferred evaluations. It's about printing. println function documented for completeness but are not guaranteed to stay in the language at all. Also Stdout and Stderr merged in one stream on Playground by design.
If you use fmt.Println(...) everywhere http://play.golang.org/p/PU3hxHCazA
or explicitly define fmt.Fprintln(os.Stdout, ... http://play.golang.org/p/OQpOQR2vm0 things would work as expected.

答案3

得分: 0

我怀疑这是Go Playground的一个错误。当我在我的机器上编译和运行这个程序时,它产生了预期的输出。有一个关于这个问题的错误报告已经提交。

英文:

I suspect this is a bug with the Go playground. When I compile and run this program on my machine, it yields the expected output. A bug report pertaining this issue has been filed.

huangapple
  • 本文由 发表于 2015年3月28日 20:08:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/29316958.html
匿名

发表评论

匿名网友

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

确定