web-dev-qa-db-fra.com

Python: exécuter un sous-processus en parallèle

J'ai le code suivant qui écrit les sommes md5 dans un fichier journal

for file in files_output:
    p=subprocess.Popen(['md5sum',file],stdout=logfile)
p.wait()
  1. Seront-ils écrits en parallèle? Par exemple, si md5sum prend beaucoup de temps pour l'un des fichiers, un autre sera-t-il démarré avant d'attendre la fin d'un précédent?

  2. Si la réponse à ce qui précède est oui, puis-je supposer que l'ordre des sommes md5 écrites dans le fichier journal peut différer en fonction du temps que prend md5sum pour chaque fichier? (certains fichiers peuvent être énormes, certains petits)

21
imagineerThat

Tous les sous-processus sont exécutés en parallèle. (Pour éviter cela, il faut attendre explicitement leur achèvement.) Ils peuvent même écrire dans le fichier journal en même temps, brouillant ainsi la sortie. Pour éviter cela, vous devez laisser chaque processus écrire dans un fichier journal différent et collecter toutes les sorties lorsque tous les processus sont terminés.

q = Queue.Queue()
result = {}  # used to store the results
for fileName in fileNames:
  q.put(fileName)

def worker():
  while True:
    fileName = q.get()
    if fileName is None:  # EOF?
      return
    subprocess_stuff_using(fileName)
    wait_for_finishing_subprocess()
    checksum = collect_md5_result_for(fileName)
    result[fileName] = checksum  # store it

threads = [ threading.Thread(target=worker) for _i in range(20) ]
for thread in threads:
  thread.start()
  q.put(None)  # one EOF marker for each thread

Après cela, les résultats doivent être stockés dans result.

18
Alfe
  1. Oui, ces processus md5sum seront démarrés en parallèle.
  2. Oui, l'ordre des écritures md5sums sera imprévisible. Et généralement, il est considéré comme une mauvaise pratique de partager une seule ressource comme un fichier à partir de nombreux processus de cette façon.

De plus, votre façon de créer p.wait() après la boucle for attendra juste que le dernier des processus md5sum se termine et que le reste d'entre eux soit toujours en cours d'exécution.

Mais vous pouvez modifier légèrement ce code pour bénéficier des avantages du traitement parallèle et de la prévisibilité de la sortie synchronisée si vous collectez la sortie md5sum dans des fichiers temporaires et la recueillez dans un fichier une fois tous les processus terminés.

import subprocess
import os

processes = []
for file in files_output:
    f = os.tmpfile()
    p = subprocess.Popen(['md5sum',file],stdout=f)
    processes.append((p, f))

for p, f in processes:
    p.wait()
    f.seek(0)
    logfile.write(f.read())
    f.close()
19
dkz

Un moyen simple de collecter la sortie des sous-processus parallèles md5sum consiste à utiliser un pool de threads et à écrire dans le fichier à partir du processus principal:

from multiprocessing.dummy import Pool # use threads
from subprocess import check_output

def md5sum(filename):
    try:
        return check_output(["md5sum", filename]), None
    except Exception as e:
        return None, e

if __name__ == "__main__":
    p = Pool(number_of_processes) # specify number of concurrent processes
    with open("md5sums.txt", "wb") as logfile:
        for output, error in p.imap(md5sum, filenames): # provide filenames
            if error is None:
               logfile.write(output)
  • la sortie de md5sum est petite, vous pouvez donc la stocker en mémoire
  • imap conserve l'ordre
  • number_of_processes Peut être différent du nombre de fichiers ou de cœurs de processeur (des valeurs plus élevées ne signifient pas plus rapidement: cela dépend des performances relatives de IO (disques) et CPU)

Vous pouvez essayer de passer plusieurs fichiers à la fois aux sous-processus md5sum.

Vous n'avez pas besoin de sous-processus externe dans ce cas; vous pouvez calculer md5 en Python :

import hashlib
from functools import partial

def md5sum(filename, chunksize=2**15, bufsize=-1):
    m = hashlib.md5()
    with open(filename, 'rb', bufsize) as f:
        for chunk in iter(partial(f.read, chunksize), b''):
            m.update(chunk)
    return m.hexdigest()

Pour utiliser plusieurs processus au lieu de threads (pour permettre à la pure Python md5sum() de fonctionner en parallèle en utilisant plusieurs CPU), il suffit de supprimer .dummy De l'importation dans le au-dessus du code.

7
jfs