Каков вариант использования для multiprocessing.pool.Pool.apply()?

#python #parallel-processing #apply #python-multiprocessing

#python #параллельная обработка #применить #python-многопроцессорность

Вопрос:

Последние несколько дней я пытался понять многопроцессорность (не многопоточность) в Python и не понимаю цели multiprocessing.pool.Pool.apply().

Поскольку это первый метод Pool класса, я бы предположил, что он используется по умолчанию, но он кажется бесполезным в контексте многопроцессорной обработки. Документация запутанная и очень краткая и гласит:

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

  1. Блокирует ли он только процесс, который был создан для него? Если да, то это не будет проблемой для процессов, в основном связанных с процессором.

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

  1. Почему что-то подобное должно быть даже в Pool классе? Когда я впервые прочитал это, я подумал, что неправильно понял, но на самом деле кажется, что запущен только один процесс. Зачем кому-то такое поведение, если он использует класс специально для многопроцессорной обработки?

Я знаю, что есть apply_async и, похоже, это работает, но у меня все еще остается ощущение, что я, должно быть, что-то упускаю apply , и я хочу это прояснить.

Я использую код, подобный этому:

 with Pool(16) as pool:
    pool_results = []
    for city in cities:
        for street in streets:
            for house in houses:
                for room in rooms:
                    pool_results.append(pool.apply_async(func=simulate, args=(city, street, house, room)))
    [result.wait() for result in pool_results]
  

Комментарии:

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

2. Подумайте о Pool.apply том же, о чем вы думаете Thread.run (когда не переопределяете его) — как о строительном блоке, который используется под капотом для составления поведения, которое вы в конечном итоге хотите, в отличие от чего-то, что фактически обеспечивает само это поведение. Одна из вещей, которая выпадает из Python, не имеющего частных методов, заключается в том, что вы можете видеть все на поверхности — просто потому, что метод существует, не означает, что он существует с учетом прямого вызова конечного пользователя.

3. @CharlesDuffy: Я не знаком со спецификой Thread.run , но если это действительно строительный блок, то почему он должен быть частью API «высокого уровня»? Кажется плохим и запутанным выбором дизайна.

4. Если вы создаете высокоуровневый API, обычно создаются вспомогательные функции для его поддержки. Они попадают туда как часть структуры поддержки для вызовов высокого уровня; но сами по себе не являются вызовами высокого уровня. Я хочу сказать, что на самом деле здесь нет проблемы. Ничто не обязывает вас использовать вызовы, о которых вы не заботитесь, и если они оказались полезными для человека, который создавал вызовы, которые вас волнуют , зачем утверждать, что этот человек был небрежен?

5. …Я также отмечу, что синхронный apply дает возможность иметь поток внутри одного блока процесса, когда что-то происходит в члене пула в другом процессе. Это может быть удобно, если вы хотите создать свой фреймворк пользовательского интерфейса в потоковой модели с общей памятью, в то время как ваша фактическая работа выполняется в рамках многопроцессорной модели. Нередко люди предпочитают многопоточность, когда это может сойти им с рук; использование многопроцессорной обработки для переноса работы, которая удерживала бы GIL вне процесса, позволяет избежать написания традиционного кода на основе потоков, когда иначе это невозможно.