Можно ли завершить («убить») поток из потока-родителя в C++?

«Можно ли завершить («убить») поток из потока-родителя в C++?» — вопрос из категории Многопоточность, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В стандартной библиотеке C++ (std::thread) нет безопасного и переносимого способа принудительно завершить ("убить") один поток из другого. Попытка деструктора std::thread вызвать std::terminate, если поток еще присоединяем (joinable), приводит к аварийному завершению всей программы.

Правильные подходы в C++:

  1. Кооперативная отмена через флаг: Поток должен периодически проверять флаг и завершаться самостоятельно.

    #include <thread>
    #include <atomic>
    #include <chrono>
    
    std::atomic<bool> stop_flag{false};
    
    void worker_thread() {
        while (!stop_flag.load()) {
            // Выполняем работу...
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        // Аккуратная очистка ресурсов
    }
    
    int main() {
        std::thread t(worker_thread);
        // ...
        stop_flag.store(true); // Запрос на остановку
        t.join(); // Ожидаем корректного завершения
        return 0;
    }
  2. Использование std::jthread (C++20): Упрощает кооперативную отмену через std::stop_token.

    #include <thread>
    using namespace std::chrono_literals;
    
    int main() {
        std::jthread t([](std::stop_token stoken) {
            while (!stoken.stop_requested()) {
                // Работа...
                std::this_thread::sleep_for(100ms);
            }
        });
        // ...
        // Деструктор jthread автоматически запросит остановку и выполнит join
        return 0;
    }

Важно: Принудительное завершение (pthread_cancel в POSIX) крайне опасно, так как может оставить мьютексы в заблокированном состоянии, не вызвать деструкторы и привести к утечкам памяти — это противоречит идиомам C++ (RAII).