std::stop_token: A General-Purpose Cancellation Mechanism
Using std::stop_source and std::stop_token
C++20 introduces std::stop_source and std::stop_token as a mechanism for general-purpose cooperative cancellation.
The key concept is: these two work very much like subject/observer in the observer pattern
- a
std::stop_sourceis the subject of a stop-state - one or more
std::stop_tokenassociated with astd::stop_sourceare observers/views of that stop-state, i.e. one-to-many mapping
besides:
std::stop_tokenis a thread-safe observer/view of the associated stop-state, so it is safe for astd::stop_tokenon a thread different from the thread usingstop_source; and it is also safe to have severalstd::stop_tokens on varied threads- a non-empty
std::stop_tokencan be produced from a non-emptystop_sourceby callingstop_source::get_token() std::stop_tokenis cheap to copy, and all copies share the associated stop-state
The code below is a typical example of using stop_source & stop_token to signal the worker thread to quit:
1 |
|
💡 A
std::stop_source::request_stop()on an already stop-requestedstd::stop_sourceis a no-op, and the function returnstrueonly if it is the first call.
Using std::jthread
std::jthread is also introduced in C++20.
Besides auto-joining on destruction, a std::jthread also carries a std::stop_source, and it is designed to allow you to use stop_source to request the std::jthread to stop.
We can adapt the previous sample to use std::jthread:
1 |
|
If the function you pass to a std::jthread takes a std::stop_token as its first argument, the std::jthread would automatically pass an associated std::stop_token to the function.
Using std::stop_callback
If a std::stop_token is a read-only observer of a std::stop_source, then a std::stop_callback is an observer with a custom action; indeed, it looks more like an observer in traditional viewpoint of the design pattern.
1 | template<class Callback> |
std::stop_callback registers a callback function to be invoked when the associated std::stop_token is requested; and it offers several key properties:
- Type erasure: The callback function is type erased, each
std::stop_callback<F>can store a different callable type; and CTAD makes passing callback even more convenient - RAII semantics: Constructor registers the callback and destructor unregisters the callback
- Immediate invocation: If stop was already requested, the callback is invoked in constructor
- Thread safety: Callbacks are invoked synchronously on the thread which called
std::stop_source::request_stop(), provided they are registered before the stop-request; and callback registration is thread-safe
However, std::stop_callback itself is neither copyable nor movable.
1 | int main() { |
Output of one run on my computer:
1 | worker thread is doing job; tid=34768 |
Given its amazing properties, Vinnie Falco, the author of the Boost.Beast library, argues that it, in combination with stop_token, is essentially a one-shot signaling mechanism.
std::stop_source is Also Copyable
Copying from a std::stop_source will make the copy’s associated stop-state the same as the original stop-source.
Although it is a relatively rare use case, we can use this property to derive some sort of first-win-cancels-others implementation:
1 | int main() { |
Two workers share one associated stop-state, and whoever completes first will cancel the other.
Advantages Over Using Plain Atomic Flags
The biggest advantage of using std::stop_token is that the std::stop_token is recognized by the standard library as the general-purpose cancellation mechanism, so the library would continue to adopt it for cancellation.
std::condition_variable_any::wait() supports being woken up via std::stop_token since C++ 20, for which you can’t with just atomic flags:
1 | int flag = 0; |
So, in future standards, the standard library will feature using std::stop_token more heavily, building a more complete ecosystem.
For example, std::execution in C++26 also uses std::stop_token for cancellation.