英文:
Passing parameters to function closure
问题
我正在尝试理解在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)
。
英文:
I'm trying to understand the difference in Go between creating an anonymous function which takes a parameter, versus having that function act as a closure. Here is an example of the difference.
With parameter:
func main() {
done := make(chan bool, 1)
go func(c chan bool) {
time.Sleep(50 * time.Millisecond)
c <- true
}(done)
<-done
}
As closure:
func main() {
done := make(chan bool, 1)
go func() {
time.Sleep(50 * time.Millisecond)
done <- true
}()
<-done
}
My question is, when is the first form better than the second? Would you ever use a parameter for this kind of thing? The only time I can see the first form being useful is when returning a func(x, y)
from another function.
答案1
得分: 63
使用闭包与使用函数参数的区别在于共享同一变量与获取值的副本。请考虑下面的两个示例。
在闭包中,所有函数调用都将使用存储在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
Playground: http://play.golang.org/p/T5rHrIKrQv
英文:
The difference between using a closure vs using a function parameter has to do with sharing the same variable vs getting a copy of the value. Consider these two examples below.
In the Closure all function calls will use the value stored in i
. This value will most likely already reach 3 before any of the goroutines has had time to print it's value.
In the Parameter example each function call will get passed a copy of the value of i
when the call was made, thus giving us the result we more likely wanted:
Closure:
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i)
}()
}
Result:
> 3
> 3
> 3
Parameter:
for i := 0; i < 3; i++ {
go func(v int) {
fmt.Println(v)
}(i)
}
Result:
> 0
> 1
> 2
Playground: http://play.golang.org/p/T5rHrIKrQv
答案2
得分: 16
何时使用参数
如果你计划更改在函数中不想观察的变量的值,那么绝对首选第一种形式。
这是典型情况,当匿名函数位于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()
}
}()
// 做其他事情
if err = otherStuff(); err != nil {
return nil, err // res 将被关闭
}
// 一切顺利,返回 res,但是
// res 不应该被关闭,这将由调用者负责
closeres = false
return res, nil // res 不会被关闭
}
在这种情况下,GetRes()
用于打开某个资源。但在返回之前,还必须完成其他可能失败的操作。如果这些操作失败,res
必须被关闭而不返回。如果一切顺利,res
不应该被关闭并返回。
英文:
When to use parameters
Definitely the first form is preferred if you plan to change the value of the variable which you don't want to observe in the function.
This is the typical case when the anonymous function is inside a for
loop and you intend to use the loop's variables, for example:
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Println(i)
}(i)
}
Without passing the variable i
you might observe printing 10
ten times. With passing i
, you will observe numbers printed from 0
to 9
.
When not to use parameters
If you don't want to change the value of the variable, it is cheaper not to pass it and thus not create another copy of it. This is especially true for large structs. Although if you later alter the code and modify the variable, you may easily forget to check its effect on the closure and get unexpected results.
Also there might be cases when you do want to observe changes made to "outer" variables, such as:
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
}
In this case the GetRes()
is to open some resource. But before returning it other things have to be done which might also fail. If those fail, res
must be closed and not returned. If everything goes well, res
must not be closed and returned.
答案3
得分: -1
这是一个来自net/Listen的参数示例:
package main
import (
"io"
"log"
"net"
)
func main() {
// 在本地系统的所有可用单播和任播IP地址上监听TCP端口2000。
l, err := net.Listen("tcp", ":2000")
if err != nil {
log.Fatal(err)
}
defer l.Close()
for {
// 等待连接。
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
}
// 在一个新的goroutine中处理连接。
// 然后循环返回接受连接,以便可以同时处理多个连接。
go func(c net.Conn) {
// 将所有传入的数据回显。
io.Copy(c, c)
// 关闭连接。
c.Close()
}(conn)
}
}
这段代码是一个简单的TCP服务器,它监听本地系统的所有可用IP地址上的2000端口。它使用goroutine来处理每个连接,并通过回显所有传入的数据来实现简单的回显功能。
英文:
This is a example of parameter from 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)
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论