如何决定并发动作的数量?

How to decide on the amount of concurrent actions?

我目前正在编写一个编码器并且(显然)想要让它变得更快。

我有一个用于编码的工作系统(所以每个 goroutine 都在做同样的事情)但是我正在努力寻找合适数量的 goroutines 来 运行 代码。我基本上想决定在最大数量的 Goroutines 上保持 CPU 忙碌。

我脑海中闪过以下想法:

现在我尝试根据这些因素来决定使用多少 goroutine,但我不太确定如何跨平台并以可靠的方式这样做。

已经尝试:

哪个还不尽如人意。

您在评论中提到

every goroutine is reading the file that is to be encoded

当然,文件——任何文件——已经以某种方式编码:可能是纯文本,或者是 UTF-8(字节流),可能被组装成"lines" 的单位。或者它可能是一个图像流,例如 mpeg 文件,由一定数量的帧组成。或者它可能是一个由记录组成的数据库。无论其 输入 形式如何,它都包含某种基本单元,您可以将其提供给(重新)编码器。

那个单位,不管它是什么,都是一个合理的分工场所。 (如何明智,取决于它是什么。请参阅下面的分块的想法。)

假设文件由独立的行组成:然后使用scanner.Scan读取它们,并将每个传递给一个接受行的通道。分拆 N,对于一些 N,阅读频道的读者,一次一行:

ch := make(chan string)
for i := 0; i < n; i++ {
    go readAndEncode(ch)
}

// later, or immediately:
for s := bufio.NewScanner(os.Stdin); s.Scan(); {
    ch <- s.Text()
}
close(ch)

如果有 100 行和 4 个读取器,前四个 ch <- s.Text() 操作进行得很快,第五个操作暂停,直到其中一个读取器完成编码并返回读取通道。

如果单行作为一个单元太小,也许您应该一次阅读 "chunk"(例如 1 MB)。如果块末尾有部分行,请备份或阅读更多内容,直到你有整行。然后发送整个数据块。

因为通道 复制 数据,您可能希望发送对块的引用。1 这适用于任何更大的数据单元。 (行往往很短,与首先使用通道的开销相比,复制它们的开销通常不是很大。如果您的行的类型为 string,那么请参阅脚注。)

如果行或 chunk-of-lines 不是此处的正确工作单元,请找出 是什么 。将 goroutines 想象成每个人都有一份工作要做的人(或忙碌的小地鼠)。他们可以依靠其他人——另一个人或 gopher——来做更小的工作,不管那是什么;有 10 个人或 gophers 在 sub-tasks 上工作可以让主管管理他们。如果你需要做同样的工作 N 次,并且 N 不是无限的,你可以分拆 N 个 goroutines。如果 N 可能是无界的,则派生出一个固定的数字(可能基于 #cpus)并通过一个渠道提供给他们工作。


1正如 Burak Serdar 指出的那样,一些副本可以自动删除:例如,字符串实际上是 read-only 个切片。切片类型包含三个部分:指向底层数据的指针(引用)、长度和容量。复制切片会复制这三个部分,但不会复制底层数据。字符串也是如此:字符串 headers 省略了容量,因此通过通道发送字符串只会复制两个 header 字。因此,许多显而易见的 easy-to-code 数据分块方法已经非常有效。