Можно ли завершить («убить») поток из потока-родителя в 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).

Ответ 18+ 🔞

Э, слушай, тут такая тема интересная подъехала. Представь себе: ты в C++ решил потоки поразводить, а потом одного такого забияку прибить захотел. Так вот, стандартная библиотека тебе на это скажет: "Да похуй, что ты хочешь, приятель. Нет у нас такого — взять и нагло убить поток из соседнего подъезда". Серьёзно, если твой std::thread ещё жив да joinable, а ты его деструктор вызываешь — ёперный театр, вся программа накрывается медным тазом через std::terminate. Пиздец, а не выход.

Но не бзди, есть же работающие способы, не надо сразу в мантии судьи рядиться.

Нормальные человеческие подходы в 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), или "Всё за тебя сделают". Это вообще песня, если компилятор современный. Он сам, блядь, и остановку запросит, и дождётся завершения. Тебе только 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` всё сам сделает. Волнение ебать, как удобно.
        return 0;
    }

А теперь, блядь, самое важное! Сидеть и думать: "А может, ткнуть палкой через pthread_cancel?" — это чистой воды распиздяйство. Представь: поток в этот момент мьютекс держит, память выделяет, файл пишет. Его бах — и нету. А мьютекс так и остался закрытым навеки, деструкторы не вызвались, память повисла. Это ж ядрёна вошь, а не подход! В C++ так не принято, тут уважают RAII и порядок. Так что даже не думай, доверия к такому методу — ноль ебать.