Daily bit(e) C++. std::stop_source, std::stop_token, std::stop_callback
Daily bit(e) C++ #10, завершение присоединенного потока в C++20: std::stop_source
, std::stop_token
, std::stop_callback
.
C++20 представил новые возможности завершения присоединенного потока через std::stop_source
, std::stop_token
и std::stop_callback
, которые интегрируются с std::jthread
и std::condition_variable_any
.
После запуска поток не может быть завершен извне. Единственным решением является введение дополнительного канала связи, который сигнализирует о запросе на завершение. Этот набор функционала предлагает стандартную реализацию такого дополнительного канала с удобными интеграциями.
#include <thread>
using namespace std::chrono_literals;
auto t = std::jthread([](std::stop_token token){
// Запустить, пока не будет запрошена остановка:
while (!token.stop_requested())
{
std::this_thread::sleep_for(1s);
}
});
std::this_thread::sleep_for(2s);
t.request_stop(); // запрос остановки
Открыть пример на Compiler Explorer.
std::jthread
поставляется со встроенным std::stop_source
, который автоматически связывается с токеном, если вызываемый объект, переданный в std::jthread
, принимает std::stop_token
в качестве аргумента.
Дополнительно, если необходимо запустить код при запросе остановки, токен остановки может быть связан с функциями обратного вызова (callback) остановки. Однако обратные вызовы идут с оговоркой. Функция обратного вызова будет запущена либо в потоке, запрашивающем остановку, либо, если остановка уже была запрошена, функция обратного вызова будет запущена немедленно в потоке, зарегистрировавшем её.
#include <thread>
using namespace std::chrono_literals;
auto t = std::jthread([](std::stop_token token) {
std::atomic<bool> flag = false;
std::stop_callback callback(token, [&flag]{
flag = true;
});
// запустить, пока не будет запрошена остановка:
while (!flag)
{
std::this_thread::sleep_for(1s);
}
});
std::this_thread::sleep_for(3s);
// запускает в этом потоке любые
// связанные функции обратного вызова
t.request_stop();
Открыть пример на Compiler Explorer.
Наконец, полагаясь на условную переменную, мы хотели бы остановить переменную, ожидающую условия, если была запрошена остановка. Вот почему std::stop_token
также интегрируется с std::condition_variable_any
.
#include <mutex>
#include <condition_variable>
#include <thread>
using namespace std::chrono_literals;
struct Resource
{
std::mutex mux;
bool ready = false;
};
Resource resource;
auto t = std::jthread([&resource](std::stop_token token) {
// Ждать, когда ресурс будет готов
// или будет запрошена остановка:
std::unique_lock lock(resource.mux);
std::condition_variable_any().wait(lock, token,
[&resource] { return resource.ready; });
if (resource.ready)
{
// этот путь будет выбран, если остановка не запрошена
}
else
{
// этот путь будет выбран, если остановка запрошена
}
});
t.request_stop(); // запрос остановки
{ // Если мы закомментируем предыдущую строку
// и сделаем вместо неё следующее, будет выбран
// путь if (resource.ready).
std::unique_lock lock(resource.mux);
resource.ready = true;
}
Открыть пример на Compiler Explorer.