工作池的最佳大小

Optimal size of worker pool

我正在构建一个使用 goroutine 的“工作池”的 Go 应用程序,最初我启动池创建一些工作人员。我想知道多核处理器中的最佳工作人员数量是多少,例如在具有 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)

完整的实现在

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

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

相比

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

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

    TL;DR; 切勿手动设置 runtime.GOMAXPROCS

  2. Go 中的工作池是一组 goroutine,它们在作业到达时处理它们。在 Go 中有不同的处理工作池的方法。

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

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

    一名工人

    不过,作为一个更有可能的例子,假设您的工作人员正在做更多 I/O 绑定的事情——例如读取 HTTP 请求或通过 SMTP 发送电子邮件。在这种情况下,您可以合理地处理每个 CPU.

    数十个甚至数千个工人

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

最后,GOMAXPROCS 和工作池的唯一关联方式与 goroutines 与 GOMAXPROCS 的关联方式相同。来自 the docs:

The GOMAXPROCS variable limits the number of operating system threads that can execute user-level Go code simultaneously. There is no limit to the number of threads that can be blocked in system calls on behalf of Go code; those do not count against the GOMAXPROCS limit. This package's GOMAXPROCS function queries and changes the limit.

从这个简单的描述中,很容易看出 goroutine 的数量可能比 GOMAXPROCS 多得多(可能有数十万...或更多)--GOMAXPROCS 仅限制了 "operating system threads that can execute user-level Go code simultaneously"--此时不执行用户级 Go 代码的 goroutines 不算。在 I/O 中,绑定的 goroutines(例如等待网络响应的 goroutines)不执行代码。所以理论上 goroutine 的最大数量仅受系统可用内存的限制。