Что именно выполняет метод .join () модуля многопроцессорной обработки Python?

110

Изучая многопроцессорность Python (из статьи PMOTW ), я хотел бы получить некоторые разъяснения о том, что именно join()делает этот метод.

В старом руководстве от 2008 года говорится, что без p.join()вызова в приведенном ниже коде «дочерний процесс будет бездействовать и не завершаться, становясь зомби, которого вы должны убить вручную».

from multiprocessing import Process

def say_hello(name='world'):
    print "Hello, %s" % name

p = Process(target=say_hello)
p.start()
p.join()

Я добавил распечатку, PIDа также time.sleepдля проверки, и, насколько я могу судить, процесс завершается сам по себе:

from multiprocessing import Process
import sys
import time

def say_hello(name='world'):
    print "Hello, %s" % name
    print 'Starting:', p.name, p.pid
    sys.stdout.flush()
    print 'Exiting :', p.name, p.pid
    sys.stdout.flush()
    time.sleep(20)

p = Process(target=say_hello)
p.start()
# no p.join()

в течение 20 секунд:

936 ttys000    0:00.05 /Library/Frameworks/Python.framework/Versions/2.7/Reso
938 ttys000    0:00.00 /Library/Frameworks/Python.framework/Versions/2.7/Reso
947 ttys001    0:00.13 -bash

через 20 секунд:

947 ttys001    0:00.13 -bash

Поведение такое же, как и при p.join()добавлении в конец файла. Python Module of the Week предлагает очень удобочитаемое объяснение модуля ; «Чтобы дождаться, пока процесс завершит свою работу и выйдет, используйте метод join ().», Но похоже, что по крайней мере OS X все равно это делала.

Еще меня интересует название метода. Этот .join()метод что-нибудь объединяет? Соединяет ли процесс с его концом? Или он просто разделяет имя с собственным .join()методом Python ?

MikeiLL
источник
2
Насколько мне известно, он удерживает основной поток и ждет завершения дочернего процесса, а затем снова присоединяет ресурсы в основном потоке, в основном это чистый выход.
abhishekgarg
ну в этом есть смысл. Итак, фактические CPU, Memory resourcesданные отделяются от родительского процесса, а затем joinснова возвращаются после завершения дочернего процесса?
MikeiLL
да, это то, что он делает. Так что, если вы не присоединитесь к ним обратно, когда дочерний процесс завершится, он просто будет лежать как несуществующий или мертвый процесс
abhishekgarg
@abhishekgarg Это неправда. Дочерние процессы будут неявно присоединены после завершения основного процесса.
дано
@dano, я также изучаю python, и я просто поделился тем, что обнаружил в своих тестах, в моих тестах у меня был нескончаемый основной процесс, так что, возможно, поэтому я видел эти дочерние процессы как несуществующие.
abhishekgarg

Ответы:

125

join()Метод, при использовании threadingили multiprocessing, не связано с str.join()- это на самом деле не конкатенации ничего вместе. Скорее, это просто означает «дождаться завершения этого [потока / процесса]». Имя joinиспользуется потому, что multiprocessingAPI модуля должен выглядеть так же, как threadingAPI модуля, и threadingмодуль использует joinдля своего Threadобъекта. Использование этого термина joinдля обозначения «ждать завершения потока» распространено во многих языках программирования, поэтому Python также просто принял его.

Теперь причина, по которой вы видите 20-секундную задержку как с вызовом, так и без него, join()заключается в том, что по умолчанию, когда основной процесс готов к выходу, он неявно вызывает join()все запущенные multiprocessing.Processэкземпляры. Это не так четко указано в multiprocessingдокументации, как должно быть, но упомянуто в разделе «Рекомендации по программированию »:

Помните также, что недемонические процессы будут присоединяться автоматически.

Вы можете переопределить это поведение, установив daemonфлаг на , Processчтобы Trueдо начала процесса:

p = Process(target=say_hello)
p.daemon = True
p.start()
# Both parent and child will exit here, since the main process has completed.

Если вы это сделаете, дочерний процесс будет завершен, как только завершится основной процесс :

демон

Флаг демона процесса, логическое значение. Это должно быть установлено до вызова start ().

Начальное значение наследуется от процесса создания.

Когда процесс завершается, он пытается завершить все свои демонические дочерние процессы.

Дано
источник
6
Я понял, что это p.daemon=Trueбыло для «запуска фонового процесса, который работает без блокировки выхода основной программы». Но если «процесс демона завершается автоматически до выхода из основной программы», в чем именно его смысл?
MikeiLL
8
@MikeiLL В основном все, что вы хотите, чтобы происходило в фоновом режиме, пока запущен родительский процесс, но это не нужно аккуратно очищать перед выходом из основной программы. Возможно, рабочий процесс, который считывает данные из сокета или аппаратного устройства и передает эти данные обратно родительскому объекту через очередь или обрабатывает их в фоновом режиме для какой-то цели? В общем, я бы сказал, что использование daemonicдочернего процесса небезопасно, потому что процесс будет завершен без возможности очистки любых открытых ресурсов, которые у него могут быть ... (продолжение).
dano
7
@MikeiLL Лучше всего дать ребенку сигнал очистить и выйти перед выходом из основного процесса. Вы можете подумать, что имеет смысл оставить демонический дочерний процесс запущенным при выходе из родительского, но имейте в виду, что multiprocessingAPI разработан так, чтобы имитировать threadingAPI как можно точнее. Демонические threading.Threadобъекты завершаются сразу после выхода из основного потока, поэтому демонические multiprocesing.Processобъекты ведут себя точно так же.
dano
38

Без него join()основной процесс может завершиться раньше, чем дочерний процесс. Я не уверен, при каких обстоятельствах это приводит к зомбиизму.

Основная цель join()- убедиться, что дочерний процесс завершился до того, как основной процесс сделает что-либо, зависящее от работы дочернего процесса.

Этимология join()заключается в том, что это противоположность тому fork, что является общим термином в операционных системах семейства Unix для создания дочерних процессов. Один процесс «разветвляется» на несколько, а затем «объединяется» обратно в один.

Рассел Борогов
источник
2
Он использует это имя, join()потому что join()это то, что используется для ожидания threading.Threadзавершения объекта, а multiprocessingAPI предназначен для threadingмаксимально возможной имитации API.
дано
Ваше второе утверждение касается проблемы, с которой я сталкиваюсь в текущем проекте.
MikeiLL
Я понимаю, в какой части основной поток ожидает завершения подпроцесса, но разве это не противоречит цели асинхронного выполнения? Разве он не должен завершать выполнение независимо (подзадача или процесс)?
Апурва Кункулол,
1
@ApurvaKunkulol Зависит от того, как вы его используете, но join()необходим в случае, когда основному потоку нужны результаты работы подпотоков. Например, если вы визуализируете что-либо и назначаете 1/4 финального изображения каждому из 4 подпроцессов, и хотите отобразить все изображение, когда это будет сделано.
Рассел Борогов,
@RussellBorogove Ах! Я понял. Тогда значение асинхронной активности здесь немного иное. Это должно означать только тот факт, что подпроцессы предназначены для выполнения своих задач одновременно с основным потоком, в то время как основной поток также выполняет свою работу, а не просто простаивает в ожидании подпроцессов.
Апурва Кункулол,
12

Я не собираюсь подробно объяснять, что это joinзначит, но вот этимология и интуиция, лежащая в основе этого, которые должны помочь вам легче запомнить его значение.

Идея состоит в том, что выполнение « разветвляется » на несколько процессов, один из которых является главным, а остальные - рабочими (или «подчиненными»). Когда рабочие закончили, они «присоединяются» к мастеру, чтобы можно было возобновить последовательное выполнение.

joinМетод вызывает главный процесс ждать работника , чтобы присоединиться к нему. Этот метод лучше было бы назвать «ожидание», так как это фактическое поведение, которое он вызывает в мастере (и это то, что он называется в POSIX, хотя потоки POSIX также называют это «соединением»). Соединение происходит только как эффект резьбы взаимодействующей должным образом, это не то , что мастер делает .

Имена «вилка» и «объединение» используются в этом значении в многопроцессорной обработке с 1963 года .

оборота ларсманс
источник
Таким образом, в некотором смысле это использование слова joinмогло предшествовать его использованию в отношении конкатенации, а не наоборот.
MikeiLL
1
Маловероятно, что использование в конкатенации происходит от использования в многопроцессорной обработке; скорее оба значения происходят отдельно от простого английского смысла слова.
Рассел Борогов
2

join()используется для ожидания завершения рабочих процессов. Надо позвонить close()или terminate()перед использованием join().

Как и @Russell, упомянутое соединение похоже на противоположность fork (которая порождает подпроцессы ).

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

"the child process will sit idle and not terminate, becoming a zombie you must manually kill" это возможно, когда основной (родительский) процесс завершается, но дочерний процесс все еще выполняется и после завершения у него нет родительского процесса, которому можно было бы вернуть свой статус выхода.

Ани Менон
источник
2

В join()гарантирует вызов , что последующие строки кода не вызывается до завершения всех мультипроцессорных процессов.

Например, без оператора join()следующий код будет вызываться restart_program()еще до завершения процессов, что похоже на асинхронный и не является тем, что мы хотим (вы можете попробовать):

num_processes = 5

for i in range(num_processes):
    p = multiprocessing.Process(target=calculate_stuff, args=(i,))
    p.start()
    processes.append(p)
for p in processes:
    p.join() # call to ensure subsequent line (e.g. restart_program) 
             # is not called until all processes finish

restart_program()
И Сян Чонг
источник
0

Чтобы дождаться, пока процесс завершит свою работу и выйдет, используйте метод join ().

и

Примечание. Важно, чтобы процесс join () был завершен после его завершения, чтобы дать фоновому механизму время обновить состояние объекта, чтобы отразить завершение.

Это хороший пример помог мне понять это: здесь

Одна вещь, которую я заметил лично, заключалась в том, что мой основной процесс был приостановлен до тех пор, пока ребенок не завершил свой процесс с помощью метода join (), который multiprocessing.Process()в первую очередь победил мою точку зрения .

Джош
источник