问题 将参数传递给函数闭包


我试图理解Go中创建一个带参数的匿名函数与将该函数作为闭包之间的区别。这是差异的一个例子。

带参数:

func main() {
  done := make(chan bool, 1)
  go func(c chan bool) {
    time.Sleep(50 * time.Millisecond)
    c <- true
  }(done)
  <-done
}

作为封闭:

func main() {
  done := make(chan bool, 1)
  go func() {
    time.Sleep(50 * time.Millisecond)
    done <- true
  }()
  <-done
}

我的问题是,第一种形式何时优于第二种形式?你有没有使用参数来做这种事情?我唯一能看到第一种形式有用的是返回时 func(x, y) 来自另一个功能。


6696
2018-05-12 06:46


起源



答案:


使用闭包与使用函数参数之间的区别与共享相同的变量与获取值的副本有关。考虑下面这两个例子。

在里面 关闭 所有函数调用都将使用存储的值 i。在任何goroutine有时间打印它的值之前,这个值很可能已经达到3。

在里面 参数 例如,每个函数调用都会传递一个值的副本 i 在进行通话时,我们更有可能获得结果:

关闭:

for i := 0; i < 3; i++ {
    go func() {
        fmt.Println(i)
    }()
}

结果:

3
  3
  3

参数:

for i := 0; i < 3; i++ {
    go func(v int) {
        fmt.Println(v)
    }(i)
}

结果:

0
  1
  2

操场:  http://play.golang.org/p/T5rHrIKrQv


12
2018-05-12 06:59



谢谢!这就说得通了。关于我的原始代码的问题;我是否正确地说我应该按原样使用频道而不用打扰参数?似乎通过via参数传递它只会产生开销。 - Dave
欢迎:)是的,在您的情况下使用参数没有任何意义。一个频道也是“goroutine”安全的,所以说。 - ANisus


答案:


使用闭包与使用函数参数之间的区别与共享相同的变量与获取值的副本有关。考虑下面这两个例子。

在里面 关闭 所有函数调用都将使用存储的值 i。在任何goroutine有时间打印它的值之前,这个值很可能已经达到3。

在里面 参数 例如,每个函数调用都会传递一个值的副本 i 在进行通话时,我们更有可能获得结果:

关闭:

for i := 0; i < 3; i++ {
    go func() {
        fmt.Println(i)
    }()
}

结果:

3
  3
  3

参数:

for i := 0; i < 3; i++ {
    go func(v int) {
        fmt.Println(v)
    }(i)
}

结果:

0
  1
  2

操场:  http://play.golang.org/p/T5rHrIKrQv


12
2018-05-12 06:59



谢谢!这就说得通了。关于我的原始代码的问题;我是否正确地说我应该按原样使用频道而不用打扰参数?似乎通过via参数传递它只会产生开销。 - Dave
欢迎:)是的,在您的情况下使用参数没有任何意义。一个频道也是“goroutine”安全的,所以说。 - ANisus


何时使用参数

如果您打算更改您不希望在函数中观察到的变量的值,则首选表格是首选。

这是匿名函数在a中的典型情况 for 循环,你打算使用循环的变量,例如:

for i := 0; i < 10; i++ {
    go func(i int) {
        fmt.Println(i)
    }(i)
}

没有传递变量 i 你可能会观察到印刷 10 十次。随着传球 i,你会看到从中打印的数字 0 至 9

何时不使用参数

如果您不想更改变量的值,那么不传递它就更便宜,因此不会创建它的另一个副本。对于大型结构尤其如此。虽然如果稍后更改代码并修改变量,您可能很容易忘记检查其对闭包的影响并获得意外结果。

也有可能是你的情况  想要观察对“外部”变量所做的更改,例如:

func GetRes(name string) (Res, error) {
    res, err := somepack.OpenRes(name)
    if err != nil {
        return nil, err
    }

    closeres := true
    defer func() {
        if closeres {
            res.Close()
        }
    }()

    // Do other stuff
    if err = otherStuff(); err != nil {
        return nil, err // res will be closed
    }

    // Everything went well, return res, but
    // res must not be closed, it will be the responsibility of the caller
    closeres = false

    return res, nil // res will not be closed
}

在这种情况下 GetRes() 是打开一些资源。但在返回之前,必须完成其他可能也会失败的事情。如果那些失败, res 必须关闭,不得退回。如果一切顺利, res 不得关闭并退回。


4
2018-05-12 06:59



谢谢你。这个和另一个答案真的帮助我澄清了我的理解。很好的例子。就我而言,使用频道,我想我只会使用封闭风格。 - Dave