Оптимальный размер пула рабочих

Я создаю приложение Go, которое использует рабочий пул горутин, сначала я запускаю пул, создавая несколько рабочих. Мне было интересно, какое было бы оптимальное количество рабочих в многоядерном процессоре, например в ЦП с 4 ядрами? В настоящее время я использую следующий подход:

    // 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) и module.Dispatcher.Run (jobQueue)

Мой вариант использования рабочего пула: у меня есть служба, которая принимает запросы и вызывает несколько внешних API и объединяет их результаты в один ответ. Каждый вызов может выполняться независимо от других, так как порядок результатов не имеет значения. Я отправляю вызовы в рабочий пул, где каждый вызов выполняется в одной доступной горутине асинхронным способом. Мой поток запросов продолжает прослушивать каналы возврата при извлечении и агрегировании результатов, как только рабочий поток завершен. Когда все будет сделано, в качестве ответа возвращается окончательный агрегированный результат. Поскольку каждый внешний вызов API может отображать переменное время ответа, некоторые вызовы могут быть выполнены раньше, чем другие. Насколько я понимаю, выполнение этого параллельным способом было бы лучше с точки зрения производительности, по сравнению с тем, чтобы делать это синхронным способом, вызывая каждый внешний API один за другим.


person guilhebl    schedule 24.12.2017    source источник
comment
На это нет объективного ответа. Это полностью зависит от того, что вы делаете со своими рабочими, и от того, к каким характеристикам вы стремитесь. Если ваши рабочие процессы привязаны к вводу-выводу (например, HTTP или SMTP-сервер), вы, вероятно, могли бы обрабатывать тысячи рабочих на одном компьютере с ЦП. Если они привязаны к ЦП, вы можете не увидеть никаких преимуществ, кроме одного рабочего на ЦП.   -  person Flimzy    schedule 24.12.2017
comment
Кроме того, нет причин устанавливать GOMAXPROCS в 99,99% современных приложений Go. И установка числа процессоров + 1 никогда не имеет смысла, AFAIK. Вам следует установить это только в том случае, если вы хотите уменьшить значение по умолчанию.   -  person Flimzy    schedule 24.12.2017
comment
Это зависит от того, какую проблему вы решаете (с привязкой к ЦП или иначе), и от срока службы каждой из ваших задач, которую будет обрабатывать рабочий поток. Вам нужно попробовать несколько чисел, провести сравнительный анализ и посмотреть, что лучше всего подходит для вашего варианта использования.   -  person Ravi    schedule 24.12.2017


Ответы (1)


Комментарии в вашем примере кода предполагают, что вы, возможно, объединяете две концепции GOMAXPROCS и рабочий пул. Эти две концепции полностью различны в Go.

  1. GOMAXPROCS устанавливает максимальное количество потоков ЦП, которое будет использовать среда выполнения Go. По умолчанию это количество ядер ЦП, обнаруженных в системе, и его почти никогда не следует изменять. Единственный раз, когда я могу придумать, чтобы это изменить, - это если вы хотите явно ограничить программу Go по какой-то причине, чтобы она использовала меньше, чем доступные процессоры, тогда вы можете установить это, например, на 1, даже при работе на 4- основной процессор. Это должно иметь значение только в редких случаях.

    TL; DR; Никогда не устанавливайте runtime.GOMAXPROCS вручную.

  2. Пулы рабочих в Go - это набор горутин, которые обрабатывают задания по мере их поступления. В Go есть разные способы обработки пулов рабочих.

    Какое количество рабочих следует использовать? Нет объективного ответа. Вероятно, единственный способ узнать это - протестировать различные конфигурации, пока не найдете ту, которая соответствует вашим требованиям.

    В качестве простого случая предположим, что ваш рабочий пул выполняет что-то очень ресурсоемкое. В этом случае вам, вероятно, понадобится по одному рабочему на процессор.

    Однако в качестве более вероятного примера допустим, что ваши рабочие выполняют что-то большее, связанное с вводом-выводом, например чтение HTTP-запросов или отправку электронной почты через SMTP. В этом случае вы можете разумно обрабатывать десятки или даже тысячи рабочих на один процессор.

    Кроме того, возникает вопрос, следует ли вам вообще использовать пул рабочих. Для большинства задач Go не требуются рабочие пулы. Я работал над десятками производственных программ Go и ни разу не использовал рабочий пул ни в одной из них. Я также много раз писал больше одноразовых инструментов Go и использовал рабочий пул только один раз.

И, наконец, единственный способ, которым связаны GOMAXPROCS и рабочие пулы, - это то же самое, как горутины относятся к GOMAXPROCS. Из документов:

Переменная GOMAXPROCS ограничивает количество потоков операционной системы, которые могут одновременно выполнять код Go на уровне пользователя. Нет ограничений на количество потоков, которые могут быть заблокированы в системных вызовах от имени кода Go; они не учитываются при ограничении GOMAXPROCS. Функция GOMAXPROCS этого пакета запрашивает и изменяет ограничение.

Из этого простого описания легко увидеть, что может быть намного больше (потенциально сотни тысяч ... или больше) горутин, чем _6 _ - _ 7_ ограничивает только количество «потоков операционной системы, которые могут одновременно выполнять код Go на уровне пользователя» --goroutines, которые в данный момент не выполняют код Go на уровне пользователя, не учитываются. А в горутинах с привязкой к вводу-выводу (например, ожидающим ответа сети) код не выполняется. Таким образом, у вас есть теоретическое максимальное количество горутин, ограниченное только доступной памятью вашей системы.

person Flimzy    schedule 24.12.2017
comment
Мой код действительно реализует пул рабочих - достаточно честно, приношу свои извинения за то, что не заметил этого. Я удалил этот комментарий из своего ответа. Я считаю, что остальная часть ответа верна. - person Flimzy; 25.12.2017
comment
@guilhebl - альтернатива - не только синхронное использование одного потока. Альтернативный вариант - использовать неупольные рабочие процессы - по одной горутине на задачу - и позволить планировщику позаботиться об этом. Как объясняется в ответе, в большинстве случаев это правильное решение. - person Adrian; 26.12.2017
comment
@Adrian На основе этого сообщения marcio.io / 2015/07 / кажется, что использование пула рабочих в определенных сценариях может быть разумным с точки зрения повышения производительности. - person guilhebl; 27.12.2017
comment
Верно, и я бы никогда не сказал, что это не так. Я считаю, что простого варианта запуска горутины для каждой задачи обычно достаточно, а дополнительную сложность пула рабочих следует добавлять только в том случае, если это необходимо в конкретной ситуации и подтверждено измерениями для повышения производительности в этой ситуации. - person Adrian; 27.12.2017