Может ли поток перезапустить сам себя?

Ответ

Нет, поток в C++ (и в общем случае) не может «перезапустить» себя в прямом смысле. Объект std::thread представляет собой единичный поток выполнения. Как только функция потока завершается (возвращает управление или выбрасывает исключение), поток завершает свою работу и переходит в состояние not joinable. Повторно использовать этот же объект std::thread для запуска новой функции нельзя — это вызовет исключение std::system_error.

Что можно сделать:

  1. Завершить текущий поток и создать новый с той же логикой. Это не перезапуск, а создание нового потока.
  2. Использовать цикл внутри функции потока, чтобы он не завершался, а ждал новых задач. Это классический подход для worker-потоков в пуле.

Пример 1: Ошибочная попытка «переиспользования»:

#include <thread>
#include <iostream>

void task() {
    std::cout << "Task done.n";
}

int main() {
    std::thread th(task);
    th.join(); // Поток завершен

    // Попытка запустить поток снова через тот же объект
    th = std::thread(task); // ОШИБКА! Объект th после join() находится в невалидном состоянии.
    // Правильно: нужно создать новый объект.
    std::thread th2(task);
    th2.join();

    return 0;
}

Пример 2: Реализация worker-потока с циклом (более практичный подход):

#include <iostream>
#include <thread>
#include <atomic>
#include <queue>
#include <mutex>
#include <condition_variable>

class WorkerThread {
    std::thread thread;
    std::atomic<bool> running{true};
    std::queue<std::function<void()>> tasks;
    std::mutex queue_mutex;
    std::condition_variable cv;

    void loop() {
        while (running) {
            std::function<void()> task;
            {
                std::unique_lock lock(queue_mutex);
                // Ждем, пока не появится задача или не придет сигнал на остановку
                cv.wait(lock, [this]() { return !tasks.empty() || !running; });

                if (!running && tasks.empty()) break;

                task = std::move(tasks.front());
                tasks.pop();
            }
            task(); // Выполняем задачу
        }
        std::cout << "Worker thread finished.n";
    }

public:
    WorkerThread() : thread(&WorkerThread::loop, this) {}

    ~WorkerThread() {
        stop();
        if (thread.joinable()) thread.join();
    }

    void stop() {
        running = false;
        cv.notify_all(); // Будим поток, чтобы он мог завершиться
    }

    void post_task(std::function<void()> task) {
        {
            std::lock_guard lock(queue_mutex);
            tasks.push(std::move(task));
        }
        cv.notify_one(); // Сигнализируем потоку, что есть работа
    }
};

В этом примере поток работает в цикле (loop()), обрабатывая множество задач, и завершается только по сигналу. Это эффективнее, чем постоянное создание и уничтожение потоков.

Ответ 18+ 🔞

Ёпта, ну слушай, тут народ часто спрашивает, можно ли поток в C++ как-то перезапустить, типа как старый телевизор, когда изображение поплыло — хлопнул по боку, и снова работает. Так вот, блядь, нет, нельзя. Объект std::thread — это как одноразовый шприц, ёб твою мать. Отработал свою функцию — и всё, пиши пропало, состояние у него теперь not joinable. Если попробуешь через тот же самый объект новую функцию запустить, он тебе такое std::system_error в морду выкинет, что мало не покажется. Доверия ебать ноль к такому подходу.

Что же делать, если очень хочется?

  1. Похоронить старый поток и родить новый. Это не перезапуск, а чистой воды создание нового потока, просто логика у него будет такая же.
  2. Сделать поток вечным работягой. То есть запихнуть в его функцию бесконечный цикл, чтобы он сидел, ждал заданий и не думал сдыхать. Это самый правильный и рабочий подход для всяких пулов потоков.

Пример 1: Как НЕ НАДО делать (классический косяк новичка):

#include <thread>
#include <iostream>

void task() {
    std::cout << "Task done.n";
}

int main() {
    std::thread th(task);
    th.join(); // Поток отработал и лег костьми

    // Попытка воскресить труп
    th = std::thread(task); // ОШИБКА! Объект th после join() — это уже не поток, а так, **хуй в пальто**.
    // Правильно — взять новый, свежий объект.
    std::thread th2(task);
    th2.join();

    return 0;
}

Пример 2: Нормальный работяга-поток, который не ленится (практичный вариант):

#include <iostream>
#include <thread>
#include <atomic>
#include <queue>
#include <mutex>
#include <condition_variable>

class WorkerThread {
    std::thread thread;
    std::atomic<bool> running{true};
    std::queue<std::function<void()>> tasks;
    std::mutex queue_mutex;
    std::condition_variable cv;

    void loop() {
        while (running) {
            std::function<void()> task;
            {
                std::unique_lock lock(queue_mutex);
                // Спим, пока не появится работа или пока нас не пошлют нахуй
                cv.wait(lock, [this]() { return !tasks.empty() || !running; });

                if (!running && tasks.empty()) break;

                task = std::move(tasks.front());
                tasks.pop();
            }
            task(); // Выполняем то, что дали
        }
        std::cout << "Worker thread finished.n";
    }

public:
    WorkerThread() : thread(&WorkerThread::loop, this) {}

    ~WorkerThread() {
        stop();
        if (thread.joinable()) thread.join();
    }

    void stop() {
        running = false;
        cv.notify_all(); // Кричим всем потокам: "Подъём, **хитрая жопа**, работа закончилась!"
    }

    void post_task(std::function<void()> task) {
        {
            std::lock_guard lock(queue_mutex);
            tasks.push(std::move(task));
        }
        cv.notify_one(); // Тыкаем одного работягу: "Эй, **чувак**, дело есть!"
    }
};

Вот в этом примере поток живёт в цикле (loop()), жрёт задачи одну за другой и кончает только когда ему явно скажут. Это в овердохуища раз эффективнее, чем постоянно плодить и хоронить потоки — терпения ноль ебать на такую возню.