这里有资源泄漏吗?
Is there a resource leak here?
func First(query string, replicas ...Search) Result {
c := make(chan Result)
searchReplica := func(i int) {
c <- replicas[i](query)
}
for i := range replicas {
go searchReplica(i)
}
return <-c
}
这个函数来自 Rob Pike 在 2012 年关于 go concurrency patterns 的幻灯片。我认为这个函数有资源泄漏。由于函数 return 在第一个发送和接收对发生在通道 c 之后,其他 go 例程尝试在通道 c 上发送。所以这里存在资源泄漏。任何人都知道 golang 可以证实这一点吗?以及如何使用哪种 golang 工具检测此泄漏?
是的,你是对的(供参考,这里是link to the slide)。在上面的代码中,只有一个启动的 goroutine 将终止,其余的将挂起尝试在通道 c
.
上发送
细节:
c
是无缓冲通道
- 只有一个接收操作,在
return
语句中
- 为
replicas
的每个元素启动一个新的 goroutine
- 每个启动的 goroutine 在通道
c
上发送一个值
- 因为只有 1 个接收,一个 goroutine 将能够在其上发送一个值,其余的将永远阻塞
注意根据replicas
(也就是len(replicas)
)的元素个数:
- 如果它是
0
:First()
将永远阻塞(没有人在 c
上发送任何东西)
- 如果是
1
:将按预期工作
- 如果是
> 1
:那么它会泄漏资源
以下修改版本不会泄漏 goroutines,通过使用 non-blocking 发送(在 select
和 default
分支的帮助下):
searchReplica := func(i int) {
select {
case c <- replicas[i](query):
default:
}
}
准备好结果的第一个 goroutine 将在通道 c
上发送它,这将在 return
语句中由 goroutine 运行 First()
接收。所有其他 goroutines 当他们有结果时将尝试在通道上发送,并且 "seeing" 它没有准备好(发送会阻塞因为没有人准备好从它接收),default
分支将被选择,这样goroutine就会正常结束。
另一种解决方法是使用缓冲通道:
c := make(chan Result, len(replicas))
这样发送操作就不会阻塞。当然,只有一个(第一个发送的)值将从通道接收并返回。
请注意,如果 len(replicas)
为 0
,具有上述任何修复的解决方案仍会阻塞。为避免这种情况,First()
应该明确检查这一点,例如:
func First(query string, replicas ...Search) Result {
if len(replicas) == 0 {
return Result{}
}
// ...rest of the code...
}
一些检测泄漏的工具/资源:
func First(query string, replicas ...Search) Result {
c := make(chan Result)
searchReplica := func(i int) {
c <- replicas[i](query)
}
for i := range replicas {
go searchReplica(i)
}
return <-c
}
这个函数来自 Rob Pike 在 2012 年关于 go concurrency patterns 的幻灯片。我认为这个函数有资源泄漏。由于函数 return 在第一个发送和接收对发生在通道 c 之后,其他 go 例程尝试在通道 c 上发送。所以这里存在资源泄漏。任何人都知道 golang 可以证实这一点吗?以及如何使用哪种 golang 工具检测此泄漏?
是的,你是对的(供参考,这里是link to the slide)。在上面的代码中,只有一个启动的 goroutine 将终止,其余的将挂起尝试在通道 c
.
细节:
c
是无缓冲通道- 只有一个接收操作,在
return
语句中 - 为
replicas
的每个元素启动一个新的 goroutine
- 每个启动的 goroutine 在通道
c
上发送一个值
- 因为只有 1 个接收,一个 goroutine 将能够在其上发送一个值,其余的将永远阻塞
注意根据replicas
(也就是len(replicas)
)的元素个数:
- 如果它是
0
:First()
将永远阻塞(没有人在c
上发送任何东西) - 如果是
1
:将按预期工作 - 如果是
> 1
:那么它会泄漏资源
以下修改版本不会泄漏 goroutines,通过使用 non-blocking 发送(在 select
和 default
分支的帮助下):
searchReplica := func(i int) {
select {
case c <- replicas[i](query):
default:
}
}
准备好结果的第一个 goroutine 将在通道 c
上发送它,这将在 return
语句中由 goroutine 运行 First()
接收。所有其他 goroutines 当他们有结果时将尝试在通道上发送,并且 "seeing" 它没有准备好(发送会阻塞因为没有人准备好从它接收),default
分支将被选择,这样goroutine就会正常结束。
另一种解决方法是使用缓冲通道:
c := make(chan Result, len(replicas))
这样发送操作就不会阻塞。当然,只有一个(第一个发送的)值将从通道接收并返回。
请注意,如果 len(replicas)
为 0
,具有上述任何修复的解决方案仍会阻塞。为避免这种情况,First()
应该明确检查这一点,例如:
func First(query string, replicas ...Search) Result {
if len(replicas) == 0 {
return Result{}
}
// ...rest of the code...
}
一些检测泄漏的工具/资源: