А вы уже заметили, что в предыдущих заметках я использую std::jthread
из C++20 вместо std::thread
? И зачем?
А все очень просто: деструктор std::thread
дурной.
Везде, где может начать вызываться деструктор std::thread
, нужно втыкать
// std::thread t1;
if (t1.joinable()) { // Если вы не уверены в богатой жизненной истории
// объекта t1, обязательно выполняйте эту проверку
t1.join(); // или t1.detach()
}
Чтобы ознаменовать свое желание (или нежелание) дожидаться окончания выполнения потока.
Иначе деструктор потока повалит вашу программу, вызвав std::terminate
.
Очень удобно и очень по RAII-шному, неправда ли?
Нет, конечно, совсем везде втыкать не надо. Если вы знаете, что кто-то другой уже выполнил это заклинание, или содержимое объекта std::thread
переместили в другой объект (t2 = std::move(t1)
).
И тем более не надо просто так втыкать этот код, обращающийся к одному и тому же объекту std::thread
из разных потоков. Иначе — race condition. Надо синхронизировать.
И, конечно же, убедитесь что этот код ни в коем случае не будет выполняться параллельно с вызовом деструктора t1
; Деструктор тоже вызывает joinable
, а это опять race condition.
Собираетесь сделать обертку над std::thread
, чтобы вызывать join
в ее деструкторе? Спешу порадовать: join
/detach
кидают исключения. Со всеми вытекающими проблемами.
Здорово, да? Поэтому в примерах был и будет std::jthread
. Его деструктор сам выполняет join
и снимает хотя бы часть головной боли.
А если вас join
не устраивает, не хотите ждать и пользуетесь detach
... Ну что ж. Право ваше. Только помните, что все потоки резко и внезапно помрут, когда закончится main
.