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


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

2 53

2 ответа:

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

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

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

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

таким образом, программа выходит в таком случае, другие потоки не могут продолжаться бегущий.

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

конечно, некоторые могут возразить, что если мы просто detached каждый поток, который мы запускаем, мы в безопасности на этой второй точке. Проблема в том, что в некоторых ситуациях join - самый разумная вещь, чтобы сделать. Например, "наивное" распараллеливание quicksort требует подождать, пока подзадачи не закончатся. В таких ситуациях 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