Si j'ai un programme C++ 11 exécutant 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 d'une mort ardente? Le thread où l'exception est levée mourra-t-il seul (et si oui, puis-je obtenir l'exception dans ce cas)? Quelque chose d'autre entièrement?
Rien n'a vraiment changé. Le libellé du n3290 est le suivant:
Si aucun gestionnaire correspondant n'est trouvé, la fonction
std::terminate()
est appelée
Le comportement de terminate
peut être personnalisé avec set_terminate
, mais:
Comportement requis: A
terminate_handler
mettra fin à l'exécution du programme sans retourner à l'appelant.
Ainsi, le programme se ferme dans un tel cas, les autres threads ne peuvent pas continuer à s'exécuter.
Puisqu'il semble y avoir un intérêt légitime à la propagation d'exceptions et que cela est légèrement au moins quelque peu pertinent pour la question, voici ma suggestion: std::thread
Doit être considéré comme une primitive dangereuse à construire, par ex. abstractions de niveau supérieur. Ils sont doublement risqués par exception: si une exception se déclenche à l'intérieur du thread que nous venons de lancer, tout explose, comme nous l'avons montré. Mais si une exception se déclenche dans le thread qui a lancé le std::thread
Nous pouvons potentiellement avoir des ennuis parce que le destructeur de std::thread
Nécessite que *this
Soit joint ou détaché (ou équivalent) , soyez pas un thread). Une violation de ces exigences entraîne ... un appel à std::terminate
!
Une carte de code 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 affirmer que si nous simplement detach
ed chaque thread que nous lançons, nous sommes sûrs sur ce deuxième point. Le problème avec cela 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 la fin des sous-tâches. Dans ces situations, join
sert de primitive de synchronisation (un rendez-vous).
Heureusement pour nous, ces abstractions de niveau supérieur que j'ai mentionnées existent et sont fournies avec la bibliothèque standard. Ce sont std::async
, std::future
Ainsi que std::packaged_task
, std::promise
Et std::exception_ptr
. La version équivalente et sans danger des éléments ci-dessus:
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 thread qui a appelé async
, vous pouvez plutôt passer la balle à un autre thread:
// 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