Nous avons une fonction dans laquelle un seul thread appelle (nous l'appelons le thread principal). Dans le corps de la fonction, nous générons plusieurs threads de travail pour effectuer un travail intensif en CPU, attendons que tous les threads se terminent, puis renvoyons le résultat sur le thread principal.
Le résultat est que l'appelant peut utiliser la fonction naïvement, et en interne, il utilisera plusieurs cœurs.
Tout va bien jusqu'à présent ..
Le problème que nous avons est celui des exceptions. Nous ne voulons pas que les exceptions sur les threads de travail bloquent l'application. Nous voulons que l'appelant de la fonction puisse les attraper sur le thread principal. Nous devons intercepter les exceptions sur les threads de travail et les propager vers le thread principal pour qu'elles continuent à se dérouler à partir de là.
Comment peut-on le faire?
Le mieux auquel je puisse penser est:
- Attrapez toute une variété d'exceptions sur nos threads de travail (std :: exception et quelques-unes de nos propres).
- Enregistrez le type et le message de l'exception.
- Avoir une instruction switch correspondante sur le thread principal qui relance les exceptions de tout type enregistré sur le thread de travail.
Cela présente l'inconvénient évident de ne prendre en charge qu'un ensemble limité de types d'exceptions et nécessiterait une modification chaque fois que de nouveaux types d'exceptions seraient ajoutés.
la source
Actuellement, le seul moyen portable est d'écrire des clauses catch pour tous les types d'exceptions que vous pourriez souhaiter transférer entre les threads, de stocker les informations quelque part à partir de cette clause catch et de les utiliser plus tard pour renvoyer une exception. C'est l'approche adoptée par Boost. .
En C ++ 0x, vous pourrez intercepter une exception avec
catch(...)
, puis la stocker dans une instance d'std::exception_ptr
utilisationstd::current_exception()
. Vous pouvez ensuite le relancer ultérieurement à partir du même fil ou d'un autre fil avecstd::rethrow_exception()
.Si vous utilisez Microsoft Visual Studio 2005 ou version ultérieure, la bibliothèque de threads just :: thread C ++ 0x prend en charge
std::exception_ptr
. (Avertissement: c'est mon produit).la source
Si vous utilisez C ++ 11, vous
std::future
pouvez alors faire exactement ce que vous recherchez: il peut intercepter automatiquement les exceptions qui arrivent en haut du thread de travail et les transmettre au thread parent au moment oùstd::future::get
est appelé. (Dans les coulisses, cela se produit exactement comme dans la réponse de @AnthonyWilliams; cela vient d'être implémenté pour vous déjà.)L'inconvénient est qu'il n'y a pas de méthode standard pour "arrêter de se soucier de" a
std::future
; même son destructeur bloquera simplement jusqu'à ce que la tâche soit terminée. [EDIT, 2017: Le comportement bloquant-destructeur est un dysfonctionnement uniquement des pseudo-futurs retournésstd::async
, que vous ne devriez jamais utiliser de toute façon. Les futurs normaux ne bloquent pas leur destructeur. Mais vous ne pouvez toujours pas «annuler» les tâches si vous utilisezstd::future
: la ou les tâches de réalisation de promesses continueront à s'exécuter dans les coulisses même si personne n'écoute plus la réponse.] Voici un exemple de jouet qui pourrait clarifier ce que je signifier:J'ai juste essayé d'écrire un exemple de travail en utilisant
std::thread
etstd::exception_ptr
, mais quelque chose ne va pas avecstd::exception_ptr
(en utilisant libc ++) donc je ne l'ai pas encore fait fonctionner. :([EDIT, 2017:
Je n'ai aucune idée de ce que j'ai fait de mal en 2013, mais je suis sûr que c'était de ma faute.]
la source
f
et ensuiteemplace_back
? Ne pourrais-tu pas juste fairewaitables.push_back(std::async(…));
ou est-ce que je néglige quelque chose (il compile, la question est de savoir si cela pourrait fuir, mais je ne vois pas comment)?wait
ingérer? Quelque chose du genre «dès qu'un des travaux a échoué, les autres n'ont plus d'importance».async
renvoie un futur plutôt que quelque chose d'autre). Re "Aussi, y a-t-il": Pas dedansstd::future
, mais voir le discours de Sean Parent "Better Code: Concurrency" ou mon "Futures from Scratch" pour différentes façons de l'implémenter si cela ne vous dérange pas de réécrire la totalité de la STL pour commencer. :) Le terme de recherche clé est «annulation».Votre problème est que vous pourriez recevoir plusieurs exceptions, provenant de plusieurs threads, car chacune pourrait échouer, peut-être pour des raisons différentes.
Je suppose que le thread principal attend d'une manière ou d'une autre la fin des threads pour récupérer les résultats, ou vérifie régulièrement la progression des autres threads, et que l'accès aux données partagées est synchronisé.
Solution simple
La solution simple serait de capturer toutes les exceptions dans chaque thread, de les enregistrer dans une variable partagée (dans le thread principal).
Une fois tous les threads terminés, décidez quoi faire avec les exceptions. Cela signifie que tous les autres threads ont continué leur traitement, ce qui n'est peut-être pas ce que vous souhaitez.
Solution complexe
La solution la plus complexe consiste à faire vérifier chacun de vos threads à des points stratégiques de leur exécution, si une exception a été levée depuis un autre thread.
Si un thread lève une exception, il est intercepté avant de quitter le thread, l'objet d'exception est copié dans un conteneur du thread principal (comme dans la solution simple) et une variable booléenne partagée est définie sur true.
Et quand un autre thread teste ce booléen, il voit que l'exécution doit être abandonnée et s'interrompt de manière gracieuse.
Lorsque tous les threads sont abandonnés, le thread principal peut gérer l'exception selon les besoins.
la source
Une exception levée à partir d'un thread ne sera pas capturable dans le thread parent. Les threads ont des contextes et des piles différents, et généralement le thread parent n'est pas obligé de rester là et d'attendre que les enfants se terminent, afin de pouvoir intercepter leurs exceptions. Il n'y a tout simplement pas de place dans le code pour cette capture:
Vous devrez intercepter les exceptions dans chaque thread et interpréter l'état de sortie des threads du thread principal pour relancer les exceptions dont vous pourriez avoir besoin.
BTW, en l'absence d'un catch dans un thread, il est spécifique à l'implémentation si le déroulement de la pile sera fait, c'est-à-dire que les destructeurs de vos variables automatiques peuvent même ne pas être appelés avant que terminate ne soit appelé. Certains compilateurs le font, mais ce n'est pas obligatoire.
la source
Pourriez-vous sérialiser l'exception dans le thread de travail, la transmettre au thread principal, la désérialiser et la renvoyer? Je m'attends à ce que pour que cela fonctionne, les exceptions devraient toutes dériver de la même classe (ou du moins d'un petit ensemble de classes avec à nouveau l'instruction switch). De plus, je ne suis pas sûr qu'ils seraient sérialisables, je pense juste à voix haute.
la source
Il n'y a, en effet, aucun moyen générique et bon de transmettre des exceptions d'un thread à l'autre.
Si, comme il se doit, toutes vos exceptions dérivent de std :: exception, vous pouvez avoir une capture d'exception générale de niveau supérieur qui enverra d'une manière ou d'une autre l'exception au thread principal où elle sera à nouveau lancée. Le problème est que vous perdez le point de départ de l'exception. Vous pouvez probablement écrire du code dépendant du compilateur pour obtenir ces informations et les transmettre.
Si toutes vos exceptions n'héritent pas de std :: exception, alors vous avez des problèmes et devez écrire beaucoup de captures de niveau supérieur dans votre thread ... mais la solution tient toujours.
la source
Vous devrez faire une capture générique pour toutes les exceptions dans le worker (y compris les exceptions non std, comme les violations d'accès), et envoyer un message du thread de travail (je suppose que vous avez une sorte de messagerie en place?) Au contrôle thread, contenant un pointeur en direct vers l'exception, et y renvoyer en créant une copie de l'exception. Ensuite, le travailleur peut libérer l'objet d'origine et sortir.
la source
Voir http://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_exception_ptr.html . Il est également possible d'écrire une fonction wrapper de la fonction que vous appelez pour rejoindre un thread enfant, qui relance automatiquement (en utilisant boost :: rethrow_exception) toute exception émise par un thread enfant.
la source