这里有资源泄漏吗?

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))的元素个数:

  • 如果它是 0First() 将永远阻塞(没有人在 c 上发送任何东西)
  • 如果是 1:将按预期工作
  • 如果是> 1:那么它会泄漏资源

以下修改版本不会泄漏 goroutines,通过使用 non-blocking 发送(在 selectdefault 分支的帮助下):

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...
}

一些检测泄漏的工具/资源: