#опытным
Более менее всех опытные плюсовики знают классический подход к graceful остановке воркера. Это атомарный флаг, цикл внутри рабочей функции с проверкой и деструктор с установкой флага. Выглядит все примерно так:
class Worker {
public:
Worker() : t([this] {
while (!stop_flag.load(std::memory_order_relaxed)) {
std::cout << "Working\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::cout << "Stopped\n";
}) {}
~Worker() {
stop();
}
void stop() {
stop_flag.store(true, std::memory_order_relaxed);
if (t.joinable()) {
t.join();
}
}
private:
std::atomic<bool> stop_flag = false;
std::thread t;
};Конечно, там нужно еще куча кода, чтобы заставить воркер полезную работу делать, но сейчас это не важно.
Важно, что есть тред и есть отдельная атомарная переменная, сигнализирующая об останове.
Но мы(и С++) выросли и появился новый инструмент останова воркера - std::jthread + std::stop_token.
Да. std::jthread - это не просто тред, который джойнится в деструкторе. Это тред с вшитой функциональностью остановки.
В отличие от std::thread, jthread содержит внутреннее приватное поле типа std::stop_source, которое хранит разделяемое состояние остановки потока. Из этого stop_source можно создать токены std::stop_token, через которые можно мониторить, остановится поток или нет. Грубо говоря, std::stop_source это ручка, за которую можно дернуть и перевести все ассоциированые с ней токены в состояние "остановка запрошена".
С токенами можно и отдельно работать, а можно использовать чисто внутри jthread.
class Worker {
public:
Worker() : t([this](std::stop_token st) {
while (!st.stop_requested()) {
// полезная работа...
std::cout << "Working\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::cout << "Stopped cleanly\n";
}) {}
// Деструктор автоматически вызовет request_stop() и join()
private:
std::jthread t;
};Конструктор jthread принимает функцию, первым аргументом которой является std::stop_token; этот токен будет передан объектом jthread из его внутреннего std::stop_source. Это позволяет функции проверять во время выполнения, был ли запрошен останов, и завершаться, если запрос поступил.
Ручка std::stop_source дергается в момент вызова деструктора соответствующего jthread'а и сигнализирует всем токенам, что останов запрошен.
То есть начиная с С++20 механизм останова полностью встроен внутрь самого объекта и никаких дополнительных сущностей вводить не нужно.
Evolve. Stay cool.
#cpp20 #concurrency