Jthread, Stop Tokens, and Latches
In this article I use jthread, stop tokens and latches to coordinate a worker pool
Linux /g++ 11.1.0
Overview
In this article we look at the following C++20 features:
- jthread
- stop_token
- latch
The example I'm using to showcase these is launching 10 threads and having each of the workers wait for 5 seconds. Once each of their timers completes they inform the latch. The main thread waits until all thread are complete by waiting on the latch. To showcase the stop token I also stop 2 of the threads after only 1 second of running.
This is the worker function:
void worker(const std::stop_token& stopToken, std::latch& worker_latch){
auto iterations = 5;
while (iterations > 0) {
if (stopToken.stop_requested()) {
cout << "stop token triggered" << endl;
break;
}
this_thread::sleep_for(1s);
iterations--;
}
worker_latch.count_down();
}
And this is the main function that triggers each of the workers.
void main() {
const auto thread_count = 10;
auto worker_latch = std::latch{thread_count};
auto threads = std::vector<std::jthread>();
for(int i = 0; i < thread_count; i++) {
threads.emplace_back(jthread(worker, std::ref(worker_latch)));
}
cout << "All jobs triggered" << endl;
this_thread::sleep_for(1s);
cout << "Stop 4 and 5" << endl;
threads[4].request_stop();
threads[5].request_stop();
worker_latch.wait();
cout << "All jobs completed" << endl;
return 0;
}
std::latch
#include <latch>
Latch is a single use synchronization tool that is given a starting number and can be waited on until it has been decremented to zero. In our example I trigger 10 threads and give each a reference to the latch. The main thread the calls latch.wait()
. Once each of the workers finishes its job (sleeps 5 seconds) it will call latch.count_down()
and returns (closing the thread). Latches are easy to use, but cannot be reset. For a reusable latch you could look at std::barrier.
Latch also has another handy function arrive_and_wait()
which decrements the counter and then waits until the rest are completed. This could be used in the scenario where a thread does some work and then needs to wait until all of the others threads have also reached the same stage before proceeding.
std::jthread and std::stop_token
#include <thread>
std::jthread (joining thread) is a C++20 alternative to std::thread. I make use of 2 new features of jthread in this example:
- Automatic joining
- Stop token
The first item is a bit of a freebie, and responsible for the jthread namesake. The previous behaviour for std::thread was to call std::terminate in its destructor if the thread has not been joined or detached. With std:jthread the thread is automatically joined in the destructor and we no longer get std::terminate.
The second item is std::stop_token which is an internal object of jthread that allows you to signal to the thread that it should stop what it is doing. This behavior needs to be implemented by the spawned thread as shown above. Using the std::stop_token reference I regularly check the status of this variable using stopToken.stop_requested()
. If triggered the thread will exit on its current iteration. This functionality is why I have chose to sleep 1 second 5 times as opposed to 5 seconds 1 time.