Поскольку кажется, что существует законный интерес к распространению исключений, и это, по крайней мере, несколько относится к вопросу, вот мое предложение: 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