Если у меня есть программа С++ 11, работающая на двух потоках, и одна из них бросает необработанное исключение, что происходит? Будет ли вся программа умирать огненной смертью? Будет ли поток, в котором исключение выбрасывается, умирает в одиночку (и если да, могу ли я получить исключение в этом случае)? Что-то еще полностью?
Что происходит, когда исключение обрабатывается в многопоточной программе С++ 11?
Ответ 1
Ничего не изменилось. Формулировка в n3290:
Если соответствующий обработчик не найден, функция
std::terminate()
называется
Поведение terminate
можно настроить с помощью set_terminate
, но:
Обязательное поведение: A
terminate_handler
завершает выполнение программы, не возвращаясь к вызывающему.
Таким образом, программа выходит в таком случае, другие потоки не могут продолжать работать.
Ответ 2
Так как, похоже, существует законный интерес к распространению исключений, и это немного, по крайней мере, несколько имеет отношение к вопросу, здесь мое предложение: std::thread
следует считать небезопасным примитивом для построения, например. абстракции более высокого уровня. Они вдвойне рискованны по исключениям: если исключение выходит из потока, который мы только что запустили, все, как мы показали, все взрывается. Но если исключение исчезнет в потоке, который запустил std::thread
, у нас могут быть проблемы, потому что std::thread
destructor требует, чтобы *this
был либо соединен, либо отсоединен (или, что то же самое, быть не-нитью). Нарушение этих требований приводит к... вызову std::terminate
!
Кодовая карта опасностей 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
Конечно, некоторые могут утверждать, что если мы просто detach
отредактируем каждый поток, который мы запускаем, мы будем в безопасности на этой второй точке. Проблема в том, что в некоторых ситуациях join
- самая разумная вещь. Например, "наивная" распараллеливание quicksort требует подождать, пока подзадачи не закончатся. В таких ситуациях join
служит в качестве примитива синхронизации (rendez-vous).
К счастью для нас, те абстракции более высокого уровня, о которых я говорил, существуют и поставляются со стандартной библиотекой. Они std::async
, std::future
, а также std::packaged_task
, std::promise
и std::exception_ptr
. Эквивалентная, исключающая исключение версия выше:
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();
И фактически вместо вызова get
в потоке, который называется async
, вы можете вместо этого передать доллар в другой поток:
// 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