让golang在所有goroutine完成后关闭已使用的通道

8

我正在尝试运行一些goroutine,并将它们的结果提供给一个通道。我需要一个好的方法,在所有goroutines完成后让通道关闭。

我第一次尝试是在产生所有goroutine后关闭它,但我认为在所有goroutine发送其结果之前,通道已经关闭了。

for i:=0; i<=10;i++{
  go func(){
    result:=calculate()
    c<-result
  }()
}
close(c)
for result:= range c{
  all_result=append(all_result, result...)
}

然后,我的第二次尝试是计算线程并在没有线程运行时关闭它。
for i:=0; i<=10;i++{
  go func(){
    atomic.AddUint64(&go_routine_count, 1)
    result:=calculate()
    c<-result
    atomic.AddUint64(&rt_count, ^uint64(0))
  }()
}
go func(){
  for{
    // some little time to let above goroutine count up go_routine_count before this goroutine can actually check go_routine_count==0
    time.Sleep(time.Millisecond)
    go_current_routine_count:=atomic.LoadUint64(&go_routine_count)
    if go_routine_count==0{
      close(c)
    }
  }
}()
for result:= range c{
  all_result=append(all_result, result...)
}

这个方法可以用,不过可能有更正确或者更有效的方式。而且,在一些情况下,如果后面的计数检查协程在循环中的协程之前运行,这种方法就无法工作。

是否有更好的方法?

1个回答

29

sync.WaitGroup类型应该封装您想要完成的操作,而不需要使用sleep调用或忙等待。它允许您等待任意数量的任务,而不必担心它们在何种顺序下完成。

以您的原始示例为例,您可以像下面这样使用wait group:

    var wg sync.WaitGroup
    for i := 0; i <= 10; i++ {
        wg.Add(1)
        go func(){
            result := calculate()
            c <- result
            wg.Done()
        }()
    }

    // Close the channel when all goroutines are finished
    go func() {
        wg.Wait()
        close(c)
    }()

    for result := range c {
        all_result = append(all_result, result...)
    }

3
谢谢你提供这个好的解决方案。我在想,如果我使用这个方案,是否真的需要后面的goroutine来关闭通道。因此,我尝试了不带goroutine的wg.Wait(); close(c),但它给了我一个死锁错误。这是为什么? - A-letubby
4
在最后的for循环中开始读取数值之前,你的goroutine将被阻塞在尝试向通道“c”写入值的操作上。由于它正在等待wg.Done()调用,wg.Wait()调用也会被阻塞。因此,在这种情况下,你确实需要额外的goroutine。 - James Henstridge
James 是对的,正确的方法是使用 goroutine。另外,如果您使用带缓冲的通道,例如 c := make(chan any, 10),那么您就不会遇到这个错误,因为通道会在关闭和排空之前保留数据。 - Isaac Weingarten

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接