Что происходит, когда исключение не обрабатывается в многопоточной программе на C ++ 11? - PullRequest
57 голосов
/ 01 сентября 2011

Если у меня есть программа на C ++ 11, выполняющая два потока, и один из них выдает необработанное исключение, что произойдет? Будет ли вся программа умереть огненной смертью? Умирает ли поток, в котором выбрасывается исключение, один (и если да, могу ли я получить исключение в этом случае)? Что-то еще целиком?

Ответы [ 2 ]

48 голосов
/ 01 сентября 2011

Ничего действительно не изменилось. Формулировка в n3290:

Если соответствующий обработчик не найден, функция std::terminate() называется

Поведение terminate можно настроить с помощью set_terminate, но:

Обязательное поведение : terminate_handler должен прекратить выполнение программы без возврата к вызывающей стороне.

Таким образом, в этом случае программа завершается, другие потоки не могут продолжать работать.

27 голосов
/ 01 сентября 2011

Поскольку кажется, что существует законный интерес к распространению исключений, и это, по крайней мере, несколько относится к вопросу, вот мое предложение: std::thread следует считать небезопасным примитивом для построения, например. абстракции высшего уровня. Они вдвойне рискованно в отношении исключений: если исключение срабатывает в только что запущенном потоке, все взрывается, как мы показали. Но если исключение возникает в потоке, который запустил std::thread, мы потенциально можем столкнуться с проблемами, потому что деструктор std::thread требует, чтобы *this был либо присоединен, либо отсоединен (или, что эквивалентно, будет not-a- нить ). Нарушение этих требований приводит к ... звонку на 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 является наиболее разумной вещью. Например, для «наивного» распараллеливания быстрой сортировки требуется дождаться окончания подзадач. В этих ситуациях join служит примитивом синхронизации (рандеву).

К счастью для нас, упомянутые мною абстракции более высокого уровня существуют и поставляются со Стандартной библиотекой. Это 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
...