web-dev-qa-db-fra.com

Quand appeler .join () sur un processus?

Je lis différents didacticiels sur le module multiprocessing en Python et j'ai du mal à comprendre pourquoi/quand appeler process.join(). Par exemple, je suis tombé sur cet exemple:

nums = range(100000)
nprocs = 4

def worker(nums, out_q):
    """ The worker function, invoked in a process. 'nums' is a
        list of numbers to factor. The results are placed in
        a dictionary that's pushed to a queue.
    """
    outdict = {}
    for n in nums:
        outdict[n] = factorize_naive(n)
    out_q.put(outdict)

# Each process will get 'chunksize' nums and a queue to put his out
# dict into
out_q = Queue()
chunksize = int(math.ceil(len(nums) / float(nprocs)))
procs = []

for i in range(nprocs):
    p = multiprocessing.Process(
            target=worker,
            args=(nums[chunksize * i:chunksize * (i + 1)],
                  out_q))
    procs.append(p)
    p.start()

# Collect all results into a single result dict. We know how many dicts
# with results to expect.
resultdict = {}
for i in range(nprocs):
    resultdict.update(out_q.get())

# Wait for all worker processes to finish
for p in procs:
    p.join()

print resultdict

D'après ce que je comprends, process.join() bloquera le processus d'appel jusqu'à ce que le processus dont la méthode de jointure a été appelée ait terminé son exécution. Je crois également que les processus enfants qui ont été démarrés dans l'exemple de code ci-dessus terminent l'exécution à la fin de la fonction cible, c'est-à-dire après avoir poussé leurs résultats vers le out_q. Enfin, je crois que out_q.get() bloque le processus d'appel jusqu'à ce qu'il y ait des résultats à tirer. Ainsi, si vous considérez le code:

resultdict = {}
for i in range(nprocs):
    resultdict.update(out_q.get())

# Wait for all worker processes to finish
for p in procs:
    p.join()

le processus principal est bloqué par les appels out_q.get() jusqu'à ce que chaque processus de travail ait fini de pousser ses résultats dans la file d'attente. Ainsi, au moment où le processus principal quitte la boucle for, chaque processus enfant devrait avoir terminé son exécution, n'est-ce pas?

Si tel est le cas, existe-t-il une raison pour appeler les méthodes p.join() à ce stade? Tous les processus de travail ne sont-ils pas déjà terminés, alors comment cela fait-il que le processus principal "attende la fin de tous les processus de travail"? Je pose la question principalement parce que j'ai vu cela dans plusieurs exemples différents, et je suis curieux de savoir si je n'ai pas compris quelque chose.

30
Justin

Essayez d'exécuter ceci:

import math
import time
from multiprocessing import Queue
import multiprocessing

def factorize_naive(n):
    factors = []
    for div in range(2, int(n**.5)+1):
        while not n % div:
            factors.append(div)
            n //= div
    if n != 1:
        factors.append(n)
    return factors

nums = range(100000)
nprocs = 4

def worker(nums, out_q):
    """ The worker function, invoked in a process. 'nums' is a
        list of numbers to factor. The results are placed in
        a dictionary that's pushed to a queue.
    """
    outdict = {}
    for n in nums:
        outdict[n] = factorize_naive(n)
    out_q.put(outdict)

# Each process will get 'chunksize' nums and a queue to put his out
# dict into
out_q = Queue()
chunksize = int(math.ceil(len(nums) / float(nprocs)))
procs = []

for i in range(nprocs):
    p = multiprocessing.Process(
            target=worker,
            args=(nums[chunksize * i:chunksize * (i + 1)],
                  out_q))
    procs.append(p)
    p.start()

# Collect all results into a single result dict. We know how many dicts
# with results to expect.
resultdict = {}
for i in range(nprocs):
    resultdict.update(out_q.get())

time.sleep(5)

# Wait for all worker processes to finish
for p in procs:
    p.join()

print resultdict

time.sleep(15)

Et ouvrez le gestionnaire de tâches. Vous devriez pouvoir voir que les 4 sous-processus passent à l'état zombie pendant quelques secondes avant d'être interrompus par le système d'exploitation (en raison des appels de jointure):

enter image description here

Avec des situations plus complexes, les processus enfants peuvent rester dans un état zombie pour toujours (comme la situation dont vous parliez dans un autre question ), et si vous créez suffisamment de processus enfants, vous pouvez remplir la table de processus causant des problèmes au système d'exploitation (ce qui peut tuer votre processus principal pour éviter les échecs).

18
Bakuriu

Au point juste avant d'appeler join, tous les travailleurs ont placé leurs résultats dans leurs files d'attente, mais ils ne sont pas nécessairement revenus et leurs processus ne sont peut-être pas encore terminés. Ils peuvent ou non l'avoir fait, selon le moment.

L'appel de join garantit que tous les processus ont le temps de se terminer correctement.

18
oefe