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.
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:
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.
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:])
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.