工作池的最佳大小

gui*_*ebl 3 parallel-processing optimization go goroutine worker-pool

我正在构建一个 Go 应用程序,它使用 goroutines 的“工作池”,最初我启动池创建许多工作人员。我想知道多核处理器中的最佳工人数量是多少,例如在具有 4 核的 CPU 中?我目前正在使用以下方法:

    // init pool
    numCPUs := runtime.NumCPU()

    runtime.GOMAXPROCS(numCPUs + 1) // numCPUs hot threads + one for async tasks.
    maxWorkers := numCPUs * 4

    jobQueue := make(chan job.Job)

    module := Module{
        Dispatcher: job.NewWorkerPool(maxWorkers),
        JobQueue:   jobQueue,
        Router:     router,
    }

    // A buffered channel that we can send work requests on.
    module.Dispatcher.Run(jobQueue)
Run Code Online (Sandbox Code Playgroud)

完整的实现在下

job.NewWorkerPool(maxWorkers) 和 module.Dispatcher.Run(jobQueue)

我使用工作池的用例:我有一个服务,它接受请求并调用多个外部 API,并将它们的结果聚合到一个响应中。每个调用都可以独立于其他调用,因为结果的顺序无关紧要。我将调用分派到工作池,其中每个调用都以异步方式在一个可用的 goroutine 中完成。一旦工作线程完成,我的“请求”线程就会在获取和聚合结果的同时继续监听返回通道。完成所有操作后,最终聚合结果将作为响应返回。由于每个外部 API 调用可能呈现可变响应时间,因此某些调用可以比其他调用更早完成。

Fli*_*mzy 9

您的示例代码中的注释表明您可能将GOMAXPROCS和 工作池的两个概念混为一谈。这两个概念在 Go 中是完全不同的。

  1. GOMAXPROCS设置 Go 运行时将使用的最大 CPU 线程数。这默认为在系统上找到的 CPU 内核数,并且几乎不应该更改。我能想到的唯一一次改变是,如果您出于某种原因想明确限制 Go 程序使用少于可用 CPU 的数量,那么您可以将其设置为 1,例如,即使在 4-核心 CPU。这应该只在极少数情况下重要。

    TL; 博士; 切勿runtime.GOMAXPROCS手动设置。

  2. Go 中的工作池是一组 goroutine,它们在作业到达时对其进行处理。在 Go 中有多种处理工作池的方法。

    您应该使用多少工人?没有客观的答案。可能唯一知道的方法是对各种配置进行基准测试,直到找到满足您要求的配置。

    作为一个简单的例子,假设您的工作池正在做一些非常占用 CPU 的事情。在这种情况下,您可能需要每个 CPU 一个工人。

    不过,作为一个更可能的例子,假设您的工作人员正在做一些 I/O 限制更大的事情——例如读取 HTTP 请求,或通过 SMTP 发送电子邮件。在这种情况下,您可以合理地为每个 CPU 处理数十甚至数千个工人。

    还有一个问题是你是否应该使用工作池。Go 中的大多数问题根本不需要工作池。我曾参与过数十个 Go 生产程序,但从未在其中任何一个中使用过工作池。我还多次编写了一次性使用的 Go 工具,并且可能只使用了一次工作池。

最后,GOMAXPROCS和 工作池关联的唯一方式与 goroutines 关联的方式相同GOMAXPROCS。从文档

GOMAXPROCS 变量限制了可以同时执行用户级 Go 代码的操作系统线程的数量。代表Go代码在系统调用中可以阻塞的线程数没有限制;这些不计入 GOMAXPROCS 限制。这个包的 GOMAXPROCS 函数查询和更改限制。

从这个简单的描述中,很容易看出可能有更多(可能是数十万……或更多)goroutines 比GOMAXPROCS-GOMAXPROCS仅限制“可以同时执行用户级 Go 代码的操作系统线程”的数量 -目前不执行用户级 Go 代码的 goroutine 不算在内。并且在 I/O 绑定的 goroutines 中(例如那些等待网络响应的)不执行代码。因此,理论上的最大协程数仅受系统可用内存的限制。