Поток уже завершился, но объект std::thread остаётся joinable. Что произойдёт при его уничтожении?
Если объект std::thread уничтожается в состоянии joinable, вызывается std::terminate, даже если связанный поток уже завершил выполнение. Перед уничтожением поток нужно явно присоединить через join или отделить через detach.
В C++11 управление потоком было разделено между объектом std::thread и выполняемым потоком. Деструктор не стал автоматически ждать завершения потока, поскольку неявная блокировка могла бы неожиданно остановить программу, а автоматическое отделение могло бы оставить поток обращаться к уже уничтоженным данным.
Поэтому стандарт требует от программиста явно выбрать политику завершения: дождаться потока с помощью join либо передать ему независимое выполнение через detach.
Состояние joinable означает, что объект std::thread всё ещё связан с потоком, которым он владеет. Это не означает, что поток в данный момент выполняется: он может уже завершиться, но связь с объектом не снята.
Если такой объект выходит из области видимости, его деструктор вызывает std::terminate. Это особенно опасно при исключениях: поток может быть запущен, после чего другая операция выбросит исключение, и разрушение локального объекта потока аварийно завершит программу.
После успешного join объект перестаёт быть joinable: вызывающий поток дожидается завершения целевого потока и получает гарантии синхронизации, связанные с завершением потока. После detach объект также перестаёт быть joinable, но целевой поток продолжает работать независимо.
Здесь join безопасно завершает жизненный цикл объекта t. Если заменить его удалением объекта без join или detach, будет вызван std::terminate.
Detach требует особой осторожности: отделённый поток не должен обращаться к объектам, срок жизни которых заканчивается раньше него. Поэтому для большинства задач, где результат или завершение потока важны, предпочтителен join.
В C++20 std::jthread автоматически выполняет присоединение в деструкторе и поддерживает запрос остановки. Это уменьшает риск забыть join, но деструктор jthread всё равно может блокироваться до завершения потока; автоматическое присоединение не делает операции мгновенными и не прерывает поток принудительно.
Сервис запускает рабочий поток и затем выполняет инициализацию. Если инициализация завершается исключением, обычный объект std::thread может быть уничтожен во время раскрутки стека и вызвать std::terminate.
Рассматривались два варианта. Detach устраняет необходимость ожидания, но создаёт риск обращения к уничтоженным объектам и усложняет остановку сервиса. Ручной join в каждом пути выхода надёжен, но требует аккуратного управления исключениями. Для кода на C++20 выбран std::jthread: его автоматическое присоединение защищает от забытых путей очистки, а механизм запроса остановки позволяет организовать штатное завершение. Результат — отсутствие аварийного завершения из-за забытых потоков при сохранении контролируемого времени остановки.
Означает ли joinable, что поток ещё выполняется?
Нет. После завершения функции потока связанный объект std::thread может оставаться joinable. Состояние описывает наличие связи объекта с потоком, а не текущий статус выполнения. Такой объект всё равно нужно обработать через join или detach.
Что произойдёт при повторном вызове join или detach?
После первого успешного join или detach объект становится неjoinable. Повторный вызов требует joinable-состояния и приводит к исключению std::system_error. Поэтому перед такими операциями допустимо проверять joinable(), но сама проверка не заменяет корректное владение объектом и синхронизацию доступа к нему.
Почему автоматический join не всегда является безусловно безопасным решением?
Автоматический join предотвращает вызов std::terminate, но деструктор может ждать неопределённо долго, если поток застрял на блокировке, ожидает ввода или не обрабатывает запрос остановки. Поэтому поток должен иметь понятную стратегию завершения: кооперативную остановку, ограниченное ожидание внешних ресурсов и корректное освобождение зависимостей. Автоматическое присоединение решает проблему жизненного цикла объекта, но не проблему зависшего рабочего потока.