【问题标题】:Passing parameters to function closure将参数传递给函数闭包
【发布时间】:2015-07-22 21:38:40
【问题描述】:

我试图理解 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)

【问题讨论】:

标签: go closures


【解决方案1】:

何时使用参数

如果您打算更改不想在函数中观察到的变量的值,则绝对首选第一种形式。

这是匿名函数位于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一定不能关闭和返回。

【讨论】:

  • 谢谢。这个和另一个答案真的帮助我理清了我的理解。很好的例子。在我的情况下,使用通道,我想我会使用闭包样式。
【解决方案2】:

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

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

Parameter 示例中,每个函数调用都会在调用时传递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

【讨论】:

  • 谢谢!这就说得通了。然后是关于我的原始代码的问题;我是否正确地说我应该按原样使用通道而不用担心参数?似乎通过参数传递它只会产生开销。
  • 欢迎 :) 是的,在您的情况下,使用参数没有任何意义。通道也是“goroutine”——可以说是安全的。
【解决方案3】:

这是来自 net/Listen 的参数示例

package main

import (
    "io"
    "log"
    "net"
)

func main() {
    // Listen on TCP port 2000 on all available unicast and
    // anycast IP addresses of the local system.
    l, err := net.Listen("tcp", ":2000")
    if err != nil {
        log.Fatal(err)
    }
    defer l.Close()
    for {
        // Wait for a connection.
        conn, err := l.Accept()
        if err != nil {
            log.Fatal(err)
        }
        // Handle the connection in a new goroutine.
        // The loop then returns to accepting, so that
        // multiple connections may be served concurrently.
        go func(c net.Conn) {
            // Echo all incoming data.
            io.Copy(c, c)
            // Shut down the connection.
            c.Close()
        }(conn)
    }
}

【讨论】:

    猜你喜欢
    • 2011-06-12
    • 1970-01-01
    • 1970-01-01
    • 2013-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-18
    相关资源
    最近更新 更多