web-dev-qa-db-fra.com

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

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?

59

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.

48
Ben Voigt

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 detached 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
27
Luc Danton