Trop long, je n'ai pas lu
El warnings.catch_warnings()
le gestionnaire de contexte est pas de sécurité pour les fils . Comment l'utiliser dans un environnement de traitement parallèle ?
Contexte
Le code ci-dessous résout un problème de maximisation en utilisant le traitement parallèle avec l'outil Python multiprocessing
module. Il prend une liste de widgets (immuables), les partitionne (voir Multitraitement efficace de maximisation massive par force brute en Python 3 ), trouve les maxima ("finalistes") de toutes les partitions, puis trouve le maximum ("champion") de ces "finalistes". Si je comprends correctement mon propre code (et je ne serais pas ici si c'était le cas), je partage la mémoire avec tous les processus enfants pour leur donner les widgets d'entrée, et multiprocessing
utilise un pipe au niveau du système d'exploitation et le pickling pour renvoyer les widgets finalistes au processus principal lorsque les travailleurs ont terminé.
Source du problème
Je veux attraper les avertissements de widgets redondants causés par la ré-instauration des widgets après le dépiquage. qui se produit lorsque les widgets sortent du tuyau inter-processus. Lorsque les objets widgets s'instancient, ils valident leurs propres données, en émettant des avertissements de la norme Python warnings
pour indiquer à l'utilisateur de l'application que le widget soupçonne un problème avec les données d'entrée de l'utilisateur. Parce que le dépiquage provoque l'instanciation des objets, ma compréhension du code implique que chaque objet widget est réinstancié exactement une fois si et seulement si il est finaliste après sa sortie du tuyau -- voir la section suivante pour voir pourquoi ce n'est pas correct.
Les widgets ont déjà été créés avant d'être frottés, de sorte que l'utilisateur est déjà douloureusement conscient des erreurs d'entrée qu'il a commises et ne veut pas en entendre parler à nouveau. Ce sont les avertissements que j'aimerais attraper avec la fonction warnings
du module catch_warnings()
gestionnaire de contexte (c'est-à-dire un with
).
Solutions ratées
Lors de mes tests, j'ai déterminé que les avertissements superflus étaient émis à n'importe quel moment entre ce que j'ai étiqueté comme suit Ligne A y Ligne B . Ce qui me surprend, c'est que les alertes sont émises dans des endroits autres que les environs output_queue.get()
. Cela implique pour moi que multiprocessing
envoie les widgets aux travailleurs en utilisant le décapage.
Le résultat est que la mise en place d'un gestionnaire de contexte créé par warnings.catch_warnings()
même autour de tout, de Ligne A a Ligne B et le fait de définir le bon filtre d'avertissements dans ce contexte ne permet pas d'identifier les avertissements. Cela implique pour moi que les avertissements sont émis dans les processus de travail. En plaçant ce gestionnaire de contexte autour du code du travailleur, les avertissements ne sont pas non plus détectés.
Le code
Cet exemple omet le code permettant de décider si la taille du problème est trop petite pour se préoccuper de la bifurcation des processus, de l'importation du multiprocessing et de la définition de l'algorithme de l'utilisateur. my_frobnal_counter
y my_load_balancer
.
"Call `frobnicate(list_of_widgets)` to get the widget with the most frobnals"
def frobnicate_parallel_worker(widgets, output_queue):
resultant_widget = max(widgets, key=my_frobnal_counter)
output_queue.put(resultant_widget)
def frobnicate_parallel(widgets):
output_queue = multiprocessing.Queue()
# partitions: Generator yielding tuples of sets
partitions = my_load_balancer(widgets)
processes = []
# Line A: Possible start of where the warnings are coming from.
for partition in partitions:
p = multiprocessing.Process(
target=frobnicate_parallel_worker,
args=(partition, output_queue))
processes.append(p)
p.start()
finalists = []
for p in processes:
finalists.append(output_queue.get())
# Avoid deadlocks in Unix by draining queue before joining processes
for p in processes:
p.join()
# Line B: Warnings no longer possible after here.
return max(finalists, key=my_frobnal_counter)
4 votes
> frobnicate C'est un mot si merveilleux.
5 votes
J'aimerais que nous ayons plus de questions bien formatées comme celle-ci.
1 votes
Puisque vous dites
partitions
donne des générateurs, estpartition
un générateur dans cet exemple ? Si c'est le cas, ce n'est pas un problème de décapage puisque les générateurs ne peuvent pas être décapés (et le code ne fonctionnerait pas sous Windows).0 votes
Oups, @nneonneo. J'avais tort.
my_load_balancer
renvoie en fait un générateur produisant des tuples d'ensembles. J'ai corrigé le commentaire dans le code.0 votes
Le TL;DR est un peu trompeur, puisque ce que vous voulez vraiment, c'est attraper les avertissements émis pendant le dépiquage en
multiprocessing
travailleurs. Je suppose que vous n'obtenez aucun avertissement si vous exécutez ceci sur une machine Linux ?0 votes
@nneonneo : B) Mon théorie est que les avertissements sont émis pendant le dépiquage. Est-ce que c'est correct ? Si non, alors le TL;DR signifie ce qu'il dit : Pourquoi ai-je ce problème ? B) Depuis que j'ai ajouté le code de validation/d'avertissement dans ma classe de widget, je n'ai eu accès qu'à mes machines professionnelles, qui fonctionnent sous Win7. Ma machine personnelle est un Mac mais je n'y aurai pas accès avant d'avoir à résoudre ce problème.
1 votes
Sous Windows, les objets sont décapés dans
multiprocessing
puisque Windows ne dispose pas defork()
(les processus enfants sont donc de tout nouveaux interprètes à qui il faut dire quoi faire). Sous Linux, les arguments des fonctions de travail des processus n'ont pas besoin d'être décapés, donc savoir si cela fonctionne sous Linux ou non aiderait à circonscrire le problème. Il semble que les avertissements proviennent du dépiquage, cependant.1 votes
Donc, vous avez le contrôle sur la classe du widget ? Où faites-vous la validation ? Normalement,
__init__
n'est pas appelé pendant le dé-pickling.0 votes
(Croyez-moi, j'aimerais pouvoir utiliser Linux pour mon travail, plutôt que d'attendre MINGW toute la journée). J'ai le contrôle sur la classe des widgets, et typiquement
Widget.__init__
appelleWidget.validate
comme dernière étape de l'instanciation. Vous avez raison sur le fait que Pickle n'appelle pas__init__
(il appelle simplement__new__
et remplit ensuite directement le dictionnaire d'instance), d'où mon scepticisme quant au fait que ce soit un problème de décapage.3 votes
Pouvez-vous remplacer le code de dépiquage du widget, juste à titre de test, pour voir si vous voyez toujours les avertissements ?
0 votes
@grieve, Il semble que j'obtienne un avertissement de validation avant tout appel à
__reduce__
(c'est-à-dire avant même le début du décapage). Cela exclut-il la théorie du décapage ? Il y a beaucoup, beaucoup d'appels à__reduce__
avant et après tous les autres avertissements de validation.0 votes
@wkschwartz : J'ai supposé que les erreurs se produisaient au moment du dépilage. Cependant, __reduce__ est appelé pour décaper l'objet. Pouvez-vous envisager de surcharger __set_state__ ? Pour référence : docs.python.org/library/