In this blog post, we will explore how to create a thread pool using the new std::jthread
class introduced in C++20. Thread pools are widely used in multi-threaded applications to efficiently manage and reuse threads for concurrent processing.
What is std::jthread
?
First, let’s get familiar with the std::jthread
class. It is a new addition to the C++ standard library in C++20. std::jthread
is similar to std::thread
, but with the added benefit of managing the lifecycle of the thread automatically.
Thread Pool Implementation
Now let’s move on to creating a thread pool using std::jthread
. Here’s an example implementation:
#include <iostream>
#include <vector>
#include <functional>
#include <thread>
class ThreadPool {
public:
explicit ThreadPool(size_t numThreads) : stop(false) {
threads.reserve(numThreads);
for (size_t i = 0; i < numThreads; ++i) {
threads.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex);
condition.wait(lock, [this] { return stop || !tasks.empty(); });
if (stop && tasks.empty()) {
return;
}
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
template <typename F, typename... Args>
auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> result = task->get_future();
{
std::unique_lock<std::mutex> lock(queueMutex);
if (stop) {
throw std::runtime_error("enqueue on stopped ThreadPool");
}
tasks.emplace([task]() { (*task)(); });
}
condition.notify_one();
return result;
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queueMutex);
stop = true;
}
condition.notify_all();
for (std::thread& thread : threads) {
thread.join();
}
}
private:
std::vector<std::thread> threads;
std::queue<std::function<void()>> tasks;
std::mutex queueMutex;
std::condition_variable condition;
bool stop;
};
How to Use the Thread Pool
To use the thread pool in your application, you can follow these simple steps:
- Create an instance of the
ThreadPool
class, specifying the desired number of threads in the pool. - Use the
enqueue
method to submit tasks to the thread pool. The tasks can be any callable object that returns a value. - Retrieve the result of the tasks using the returned
std::future
object.
Here’s an example usage of the thread pool:
ThreadPool pool(4);
auto task1 = pool.enqueue([]() {
std::cout << "Task 1 executed" << std::endl;
});
auto task2 = pool.enqueue([](int x, int y) {
return x + y;
}, 5, 10);
task1.get();
std::cout << "Task 2 result: " << task2.get() << std::endl;
Conclusion
In this blog post, we covered how to create a thread pool using std::jthread
. Thread pools are a powerful tool for managing and reusing threads in multi-threaded applications, leading to improved performance and resource utilization.
By utilizing the new features in C++20, such as std::jthread
, we can make our code more idiomatic and simplify the management of threads in our applications.
#threads #threadpool #stdjthread