59 votes

Que se passe-t-il lorsqu'une exception n'est pas gérée dans un programme multithread C++11 ?

Si un programme C++11 exécute deux threads et que l'un d'eux lève une exception non gérée, que se passe-t-il ? Le programme entier mourra-t-il de sa belle mort ? Le thread dans lequel l'exception est levée mourra-t-il seul (et si oui, puis-je obtenir l'exception dans ce cas) ? Quelque chose d'entièrement différent ?

51voto

Ben Voigt Points 151460

Rien n'a vraiment changé. La formulation dans le n3290 est :

Si aucun gestionnaire correspondant n'est trouvé, la fonction std::terminate() s'appelle

Le comportement de terminate peut être personnalisé avec set_terminate mais :

Comportement requis : A terminate_handler met fin à l'exécution du programme sans revenir à l'appelant.

Ainsi, le programme se termine dans un tel cas, les autres threads ne peuvent pas continuer à fonctionner.

28voto

Luc Danton Points 21421

Puisqu'il semble y avoir un intérêt légitime pour la propagation des exceptions et que cela est légèrement au moins en rapport avec la question, voici ma suggestion : std::thread est à considérer comme une primitive non sûre pour construire, par exemple, des abstractions de plus haut niveau. Ils sont doublement une exception risquée : si une exception se déclenche dans le fil de discussion que nous venons de lancer, tout s'écroule, comme nous l'avons montré. Mais si une exception se déclenche dans le fil de discussion qui a lancé l'action std::thread nous pouvons potentiellement avoir des problèmes parce que std::thread Le destructeur de l'utilisateur exige que *this être jointes ou détachées (ou, de manière équivalente, être pas-un-fil ). Une violation de ces exigences entraîne ... un appel à std::terminate !

Une carte codée des dangers de std::thread :

auto run = []
{
    // if an exception escapes here std::terminate is called
};
std::thread thread(run);

// notice that we do not detach the thread
// if an exception escapes here std::terminate is called

thread.join();
// end of scope

Bien sûr, certains peuvent prétendre que si nous avons simplement detach ed chaque fil que nous lançons, nous sommes sûrs de ce deuxième point. Le problème avec ça c'est que dans certaines situations join est la chose la plus sensée à faire. La parallélisation "naïve" de quicksort, par exemple, nécessite d'attendre que les sous-tâches soient terminées. Dans ces situations join sert de primitive de synchronisation (un rendez-vous).

Heureusement pour nous, ces abstractions de plus haut niveau que j'ai mentionnées existent et sont fournies avec la bibliothèque standard. Elles sont std::async , std::future ainsi que std::packaged_task , std::promise et std::exception_ptr . La version équivalente, à l'abri des exceptions, de ce qui précède :

auto run = []() -> T // T may be void as above
{
    // may throw
    return /* some T */;
};

auto launched = std::async(run);
// launched has type std::future<T>

// may throw here; nothing bad happens

// expression has type T and may throw
// will throw whatever was originally thrown in run
launched.get();

Et en fait, au lieu d'appeler get dans le fil qui a appelé async vous pouvez plutôt renvoyer la balle à un autre sujet :

// only one call to get allowed per std::future<T> so
// this replaces the previous call to get
auto handle = [](std::future<T> future)
{
    // get either the value returned by run
    // or the exception it threw
    future.get();
};

// std::future is move-only
std::async(handle, std::move(launched));
// we didn't name and use the return of std::async
// because we don't have to

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X