Що саме є методом .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 пропонує дуже читабельне пояснення модуля ; "Щоб зачекати, поки процес завершить свою роботу і не завершиться, використовуйте метод join ().", Але, схоже, принаймні OS X так чи інакше робила це.

Я також цікавлюсь назвою методу. Чи є тут .join()метод, що поєднує щось? Це поєднує процес з його закінченням? Або це просто поділяє ім'я з рідним .join()методом Python ?


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дочірнього процесу не дуже безпечно, оскільки процес припиняється, не дозволяючи прибирати будь-які відкриті ресурси, які він може мати .. (продовження).
Дано

7
@MikeiLL Кращою практикою було б сигналізувати дитині на прибирання та вихід до виходу з основного процесу. Ви можете подумати, що було б доцільно залишити демон-доменний процес, коли батько закінчується, але майте на увазі, що multiprocessingAPI призначений для імітації threadingAPI як можна ближче. Демонічні threading.Threadоб’єкти припиняються, як тільки виходить головна нитка, тому демонічні multiprocesing.Processоб’єкти ведуть себе так само.
Дано

38

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

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

Етимологія join()полягає в тому, що це навпаки fork, що є загальним терміном в сімейних операційних системах Unix для створення дочірніх процесів. Один процес "розщеплюється" на кілька, потім "з'єднується" назад в один.


2
Він використовує ім'я, join()тому join()що використовується для очікування завершення threading.Threadоб'єкта, а multiprocessingAPI - це максимально імітувати threadingAPI.
Дано

Ваше друге твердження стосується проблеми, з якою я маю справу в поточному проекті.
MikeiLL

Я розумію ту частину, де головний потік очікує завершення підпроцесу, але чи не є таким видом поразки мета асинхронного виконання? Чи не слід завершувати виконання самостійно (підзадача чи процес)?
Апурва Кункулол

1
@ApurvaKunkulol Залежить від того, як ви його використовуєте, але join()він потрібен у тому випадку, коли головному потоку потрібні результати роботи підрядків . Наприклад, якщо ви щось рендеруєте і присвоюєте 1/4 остаточного зображення кожному з 4 підпроцесів, і хочете відобразити все зображення, коли це зроблено.
Рассел Борогов

@RussellBorogove Ах! Я розумію. Тоді значення асинхронної діяльності тут трохи інше. Це повинно означати лише той факт, що підпроцеси призначені для виконання своїх завдань одночасно з основною ниткою, тоді як головний потік також виконує свою роботу замість того, щоб просто просто очікувати на підпроцесах.
Апурва Кункулол

12

Я не збираюсь детально пояснювати, що joinробить, але ось етимологія та інтуїція, що стоїть за нею, яка повинна допомогти вам легше запам'ятати її значення.

Ідея полягає в тому, що виконання " розщеплює " на кілька процесів, один з яких - господар, а решта робітники (або "раби"). Коли робітники закінчені, вони "приєднуються" до ведучого, щоб послідовне виконання можливо було відновлено.

The 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 (), який multiprocessing.Process()в першу чергу переміг точку мене .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.