Назначение multiprocessing.Pool.apply и multiprocessing.Pool.apply_async

См. Пример и результат выполнения ниже:

#!/usr/bin/env python3.4
from multiprocessing import Pool
import time
import os

def initializer():
    print("In initializer pid is {} ppid is {}".format(os.getpid(),os.getppid()))

def f(x):
    print("In f pid is {} ppid is {}".format(os.getpid(),os.getppid()))
    return x*x

if __name__ == '__main__':
    print("In main pid is {} ppid is {}".format(os.getpid(), os.getppid()))
    with Pool(processes=4, initializer=initializer) as pool:  # start 4 worker processes
        result = pool.apply(f, (10,)) # evaluate "f(10)" in a single process
        print(result)

        #result = pool.apply_async(f, (10,)) # evaluate "f(10)" in a single process
        #print(result.get())

Дает:

$ ./pooleg.py
In main pid is 22783 ppid is 19542
In initializer pid is 22784 ppid is 22783
In initializer pid is 22785 ppid is 22783
In initializer pid is 22787 ppid is 22783
In f pid is 22784 ppid is 22783
In initializer pid is 22786 ppid is 22783
100

Как видно из вывода: было создано 4 процесса, но только один из них действительно работал (названный f).

Вопрос: Зачем мне создавать пул из> 1 воркера и вызывать apply(), если работа f выполняется только одним процессом? То же самое и с apply_async(), потому что и в этом случае работа выполняется только одним работником.

Я не понимаю, в каких случаях могут быть полезны эти функции.

пробовать без initializer?

salparadise 28.10.2018 20:41

@salparadise Тот же результат. инициализатор не имеет значения.

Ankur Agarwal 28.10.2018 20:50

это делает для меня. Чтобы быть ясным, Pool(processes=4) as pool - это то, как я позвонил.

salparadise 28.10.2018 20:52

@salparadise После удаления инициализатора f все еще вызывает только один процесс.

Ankur Agarwal 28.10.2018 22:08
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
4
1 045
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Эта строка в вашем коде:

Pool(processes=4, initializer=initializer) as pool:  # start 4 worker processes

не Начните 4 рабочих процесса. Он просто создает пул из них, который может поддерживать одновременную работу такого большого количества из них. Такие методы, как apply(), фактически запускают отдельные процессы.

Разница в том, что apply() и apply_async() в том, что первый блокируется до тех пор, пока результат не будет готов, а второй сразу же возвращает объект «результат». Это не имеет большого значения, если вы не хотите отправлять более одной задачи на Pool за раз (что, конечно же, составляет весь смысл использования модуля multiprocessing).

Вот некоторые модификации вашего кода, показывающие, как на самом деле выполнять некоторую параллельную обработку с Pool:

from multiprocessing import Pool
import time
import os

def initializer():
    print("In initializer pid is {} ppid is {}".format(os.getpid(),os.getppid()))

def f(x):
    print("In f pid is {} ppid is {}".format(os.getpid(),os.getppid()))
    return x*x

if __name__ == '__main__':
    print("In main pid is {} ppid is {}".format(os.getpid(), os.getppid()))
    with Pool(processes=4, initializer=initializer) as pool:  # Create 4 worker Pool.
#        result = pool.apply(f, (10,)) # evaluate "f(10)" in a single process
#        print(result)
        # Start multiple tasks.
        tasks = [pool.apply_async(f, (val,)) for val in range(10, 20)]
        pool.close()  # No more tasks.
        pool.join()  # Wait for all tasks to finish.
        results = [result.get() for result in tasks]  # Get the result of each.
        print(results)

map_sync() лучше подходит для обработки чего-то вроде этого (последовательности значений), поскольку он автоматически обрабатывает некоторые детали, показанные в приведенном выше коде.

Ответ принят как подходящий

Во-первых, оба предназначены для работы с кортежами аргументов (одиночные вызовы функций), в отличие от вариантов Pool.map, которые работают с итерациями. Так что это не ошибка, когда вы наблюдаете только один процесс, используемый при однократном вызове этих функций.


Вы бы использовали Pool.apply_async вместо одной из версий Pool.map, где вам нужен более точный контроль над отдельными задачами, которые вы хотите распределить.

Версии Pool.map берут итерацию и разбивают их на задачи, где каждая задача имеет целевую функцию тем же (сопоставленную). Pool.apply_async обычно не вызывается только один раз с пулом из> 1 воркера. Поскольку это асинхронно, вы можете перебирать вручную предварительно собранные задачи и отправлять их в несколько рабочие процессы до того, как любой из них будет завершен. Ваш список задач здесь может состоять из различных целевых функций, как вы можете видеть в этом ответе здесь. Он также позволяет регистрировать обратные вызовы для результатов и ошибок, как в примере это.

Эти свойства делают Pool.apply_async довольно универсальным и лучшим инструментом для сценариев проблем необычный, которые невозможно решить с одной из версий Pool.map.


Pool.apply действительно не очень полезен на первый взгляд (и второй). Вы можете использовать его для синхронизации потока управления в сценарии, когда вы сначала запускаете несколько задач с apply_async, а затем имеете задачу, которая должна быть завершена, прежде чем вы запускаете еще один раунд задач с apply_async.

Использование Pool.apply также может означать избавление вас от необходимости создания одного дополнительного процесса для промежуточной задачи, когда у вас уже есть пул, который в настоящее время простаивает.

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

jamadagni 22.12.2019 14:16

@jamadagni Pool.apply_async() используется не только для одиночных заданий, он часто используется, например, заполнение списка "фьючерсами", которые вы получите от звонка. Смысл продажи - большая гибкость и отсутствие блокировки основного потока в родительском. Это более гибкий подход, потому что вы можете определить отдельную функцию для каждого отдельного элемента, который будет обрабатываться (пример), и у вас также есть параметр callback для действия JIT над результатом.

Darkonaut 22.12.2019 14:31

Другие вопросы по теме