【发布时间】:2018-12-11 22:20:27
【问题描述】:
假设我们要并行处理一些计算,但我们必须保证结果的顺序与计算的顺序相同:
这可以通过例如:
https://play.golang.org/p/jQbo0EVLzvX
package main
import (
"fmt"
"time"
)
func main() {
orderPutChans := make([]chan bool, 8)
orderGetChans := make([]chan bool, 8)
doneChans := make([]chan bool, 8)
for i := 0; i < 8; i++ {
orderPutChans[i] = make(chan bool, 1)
orderGetChans[i] = make(chan bool)
doneChans[i] = make(chan bool)
}
srcCh := make(chan int)
dstCh := make(chan int)
for i := 0; i < 8; i++ {
go func(j int) {
myGetCh := orderGetChans[j]
nextGetCh := orderGetChans[(j+1) % 8]
myPutCh := orderPutChans[j]
nextPutCh := orderPutChans[(j+1) % 8]
for {
_ = <- myGetCh
v, ok := <- srcCh
if !ok {
k := (j + 1) % 8
if orderGetChans[k] != nil {
orderGetChans[k] <- true
}
orderGetChans[j] = nil
break
}
nextGetCh <- true
time.Sleep(1000)
v *= v
_ = <- myPutCh
dstCh <- v
nextPutCh <- true
}
doneChans[j] <- true
}(i)
}
go func() {
for i := 0; i < 8; i++ {
_ = <- doneChans[i]
}
close(dstCh)
}()
orderGetChans[0] <- true
orderPutChans[0] <- true
go func() {
for i := 0; i < 100; i++ {
srcCh <- i
}
close(srcCh)
}()
for vv := range dstCh {
fmt.Println(vv)
}
}
可以使用通道来传递通道的读/写权限。代码很乱,看起来也不是很整洁。 Go 中是否有更简洁的方法来实现这一目标?
编辑:
我不是要求“简单”的替换,例如使用chan struct{} 或在doneChans 上使用close 以支持doneChans[i] <- true。
编辑2:
一个更简单的方法(至少就代码而言)是有一个results 数组,消费者将数据连同一个索引(这将是工作人员的 mod 数)一起发送,然后 goroutine 写入将结果发送到results[j],然后有一个 WaitGroup 等待所有操作完成(一批或多批),然后遍历结果并将它们发送到目标通道。 (可能因为虚假分享而不太好?)
【问题讨论】:
-
如果您希望结果的顺序与输入的顺序相同,请为它们提供某种索引,并在所有结果准备好后对其进行排序。并发操作的顺序是不确定的。
-
如果您有千兆字节的数据,等待 all 结果准备好是不可行的。当然可以将其分成更小的块,处理它们,然后对这些更小的块进行排序。当然,您也可以在我的版本中将内容作为“批量计算”发送。 (即发送一个整数数组,当然,这只是演示)。
-
> 并发操作的顺序是不确定的。这当然是真的。您可能会将计算拆分为可以彼此独立运行的部分,但随后必须以结果的顺序很重要并且必须与输入值的顺序相同的方式处理结果。
-
正确。需要一些同步来提供预定义的结果顺序。您可以在一个线程中按顺序执行所有操作;您可以多线程执行所有操作,然后在单个线程中进行排序,或者按照您的建议分批执行所有操作,在单个线程中对批次进行连续排序,并使用锁定来确保批次按顺序交付。为实现可预测的并发操作顺序所做的任何事情都需要同步,从而增加复杂性并降低并发性。
-
所以你不能,正如问题标题所说,“序列化 goroutines(并行化但保证排序)”。你不能一边吃一边吃蛋糕:要么是同步的,要么是并行的;要么是保证顺序 要么 异步。