Daily bit(e) C++. std::stop_source, std::stop_token, std::stop_callback

Добавлено 8 августа 2023 в 01:48

Daily bit(e) C++ #10, завершение присоединенного потока в C++20: std::stop_source, std::stop_token, std::stop_callback.

Daily bit(e) C++. 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.

Теги

C++ / CppDaily bit(e) C++STL / Standard Template Library / Стандартная библиотека шаблоновМногопоточностьПрограммирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.