Golang 多通道写入/接收排序

  • 本文关键字:排序 多通道 Golang go
  • 更新时间 :
  • 英文 :


我的具体问题是我有一个未缓冲的通道,并且正在生成多个以信号量为边界的goroutine来执行工作:

func main() {
sem := make(chan struct{}, 10) // allow ten concurrent parsers
wg := &sync.WaitGroup{}
wg.Add(1)
DoSomething("http://example.com", sem, wg)
wg.Wait()
// all done
}
func DoSomething(u string, sem chan struct{}, wg *sync.WaitGroup) {
defer wg.Done()
sem <- struct{}{}        // grab
defer func() { <-sem }() // release

var newSomethings []string
// ...
for u := range newSomethings {
wg.Add(1)
go DoSomething(u)
}
}

如果堆栈上有多个DoSomethinggoroutine,在写入sem上被阻止(或在读取时相反(当写入发生时,是否有任何顺序哪个 go 例程通过写入?我猜这是随机的,但我可以想象:

  • 它是随机的
  • 写入/接收按注册顺序进行
  • 取决于实现

我查看了一些资源,但找不到解决方案:

  • https://github.com/golang/go/issues/247
  • https://golang.org/ref/spec#Receive_operator
  • https://golang.org/ref/spec#Channel_types

我想知道这是否是未定义的和/或依赖于实现,或者这个逻辑是否位于 go 核心中的某个地方并定义?

未定义在发送操作上阻止的 goroutines 的处理顺序,但它是作为 FIFO 实现的。您可以在runtime/chan.go中看到实现,它使用链表来跟踪通道的发送者和接收者。

我们可以尝试做一个示例来显示有效的排序,如下所示:

func main() {
ch := make(chan int)
ready := make(chan int)
for i := 0; i < 10; i++ {
i := i
go func() {
ready <- 1
ch <- i
}()
<-ready
runtime.Gosched()
}
for i := 0; i < 10; i++ {
v := <-ch
if i != v {
panic("out of order!")
}
fmt.Println(v)
}
}

https://play.golang.org/p/u0ukR-5Ptw4

这在技术上仍然不正确,因为无法在发送操作中观察到阻塞,因此在下一行发送ready和发送到ch之间仍然存在竞争。我们可以尝试通过这里的runtime.Gosched调用甚至time.Sleep来消除这种情况,但是如果没有显式同步,就无法保证"之前发生"的关系。

无论如何,这会对 goroutines 进行排队并显示预期的输出顺序,如果它们尚未排队,则更有可能不按顺序处理值。

你可以从这个例子中看到,我们无法真正确定 goroutines 排队的顺序,它几乎总是不确定的,因此对此的推理在实践中通常没有用。

最新更新