Member-only story
C++20 Concurrency — Part 2: jthreads
In this part of the issue, I’ll discuss about the new std::jthread
that helps us avoid the boilerplate code for joining the conventional std::thread
in the first section. In the end, I’ll also mention about the std::swap
algorithm’s specialization introduced in C++20 to swap the underlying thread handles associated with std::jthread
.
1. std::jthread
std::jthread
is arguably the most awaited and needed concurrency feature in C++. Before its existence, the users had only access to the std::thread
‘s interface and had to rely on a boilerplate code to ensure a proper resource management with std::thread
.
The Problem
For example, a common task that the client of std::thread
had to perform was joining the thread hence ensuring also a barrier or synchronization before the thread object goes out of scope to free up the resources (hardware and operating system) consumed by the thread. For e.g. in the example below
#include <thread>void do_task(int param){
std::cout << "doing task with " << param << " from thread: "
<< std::this_thread::get_id() << std::endl;
/* do something with param1 */
}int main(){ std::thread t1{do_task, 1};
std::thread t2{do_task, 2}; t2.detach(); //boilerplate code to manage thread resource
if(t1.joinable()){ t1.join() } //joinable returns true, barrier
if(t2.joinable()){ t2.join() } //joinable returns false
}
If an instance of std::thread
is used as a part of the object, then the resource management (according to RAII) is left to destrcutor:
#include <thread>
#include <functional>class ParallelTaskWrapper{
private:
std::functional<void(int)> task;
std::thread taskThread;
public:
ParallelTaskWrapper(std::functional<void(int)> t_, int param):
task{std::move(t_)}
{
taskThread = std::thread{task, param};
} /* boilerplate code to manage thread resource in destructor */
~ParallelTaskWrapper(){
if(taskThread.joinable()){
taskThread.join()
}
};