web-dev-qa-db-fra.com

Python pool multiprocessing se bloque à la jointure?

J'essaie d'exécuter du code python sur plusieurs fichiers en parallèle. La construction est essentiellement:

def process_file(filename, foo, bar, baz=biz):
    # do stuff that may fail and cause exception

if __name__ == '__main__':
    # setup code setting parameters foo, bar, and biz

    psize = multiprocessing.cpu_count()*2
    pool = multiprocessing.Pool(processes=psize)

    map(lambda x: pool.apply_async(process_file, (x, foo, bar), dict(baz=biz)), sys.argv[1:])
    pool.close()
    pool.join()

J'ai déjà utilisé pool.map pour faire quelque chose de similaire et cela a très bien fonctionné, mais je n'arrive pas à l'utiliser ici car pool.map ne me permet pas (semble-t-il) de passer des arguments supplémentaires (et d'utiliser lambda pour cela ne fonctionnera pas car lambda ne peut pas être rassemblé).

Alors maintenant, j'essaie de faire fonctionner les choses en utilisant directement apply_async (). Mon problème est que le code semble se bloquer et ne jamais quitter. Quelques-uns des fichiers échouent avec une exception, mais je ne vois pas pourquoi ce qui ferait échouer/bloquer la jointure? Fait intéressant, si aucun des fichiers ne tombe en panne à l'exception, il se ferme proprement.

Qu'est-ce que je rate?

Edit: Lorsque la fonction (et donc un travailleur) échoue, je vois cette exception:

Exception in thread Thread-3:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 552, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 505, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/lib/python2.7/multiprocessing/pool.py", line 376, in _handle_results
    task = get()
TypeError: ('__init__() takes at least 3 arguments (1 given)', <class 'subprocess.CalledProcessError'>, ())

Si je vois même l'un d'entre eux, le processus parent du processus se bloque pour toujours, ne récoltant jamais les enfants et sortant.

34
clemej

Désolé de répondre à ma propre question, mais j'ai trouvé au moins une solution de contournement, donc si quelqu'un d'autre a un problème similaire, je veux le poster ici. J'accepterai de meilleures réponses.

Je crois que la racine du problème est http://bugs.python.org/issue94 . Cela me dit deux choses:

  • Je ne suis pas fou, ce que j'essaie de faire est vraiment censé fonctionner
  • Au moins en python2, il est très difficile, voire impossible, de ramener les "exceptions" au processus parent. Les simples fonctionnent, mais beaucoup d'autres non.

Dans mon cas, ma fonction de travail était en train de lancer un sous-processus qui faisait défaut. Cela a renvoyé l'exception CalledProcessError, qui n'est pas pickleable. Pour une raison quelconque, cela fait que l'objet pool dans le parent sort pour déjeuner et ne revient pas de l'appel à join ().

Dans mon cas particulier, je me fiche de l'exception. Tout au plus, je veux l'enregistrer et continuer. Pour ce faire, j'encapsule simplement ma fonction de travail supérieure dans une clause try/except. Si le travailleur lève une exception, il est intercepté avant d'essayer de revenir au processus parent, enregistré, puis le processus de travail se ferme normalement car il n'essaie plus d'envoyer l'exception via. Voir ci-dessous:

def process_file_wrapped(filenamen, foo, bar, baz=biz):
    try:
        process_file(filename, foo, bar, baz=biz)
    except:
        print('%s: %s' % (filename, traceback.format_exc()))

Ensuite, j'ai mon appel de fonction de carte initial process_file_wrapped () au lieu de l'original. Maintenant, mon code fonctionne comme prévu.

45
clemej

Vous pouvez réellement utiliser un functools.partial instance au lieu d'un lambda dans les cas où l'objet doit être décapé. partial les objets sont picklables depuis Python 2.7 (et dans Python 3).

pool.map(functools.partial(process_file, x, foo, bar, baz=biz), sys.argv[1:])
5
nneonneo

Pour ce que ça vaut, j'ai eu un bug similaire (pas le même) quand pool.map accroché. Mon cas d'utilisation m'a permis d'utiliser pool.terminate pour le résoudre (assurez-vous que le vôtre aussi bien avant de changer des trucs).

J'ai utilisé pool.map avant d'appeler terminate donc je sais que tout est fini, depuis le docs :

Un équivalent parallèle de la fonction intégrée map () (elle ne prend cependant en charge qu'un seul argument itérable). Il bloque jusqu'à ce que le résultat soit prêt.

Si tel est votre cas d'utilisation, cela peut être un moyen de le corriger.

3
Reut Sharabani