Throttling threads with `std::jthread`

In multi-threaded programming, it is often necessary to control the number of threads executing concurrently to prevent resource exhaustion or to optimize performance. Throttling threads refers to limiting the number of threads actively running at any given time.

With the advent of C++20, the C++ Standard Library introduced the std::jthread class, which provides a convenient way to handle threads, including thread management and cleanliness of resource handling. In this blog post, we will explore how to use std::jthread to throttle threads effectively.

The Problem

Let’s imagine a scenario where we have a collection of tasks that need to be performed concurrently by multiple threads. However, we need to limit the number of simultaneous threads to avoid overwhelming the system or causing contention. How can we achieve this?

The Solution

With std::jthread, the solution becomes straightforward. We can use a thread pool pattern, where we create a fixed number of threads and assign tasks to them in a controlled manner. Here’s an example of how this can be accomplished:

#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <condition_variable>

class ThreadPool {
public:
    explicit ThreadPool(size_t numThreads)
        : threads(numThreads)
    {
        for (size_t i = 0; i < numThreads; ++i) {
            threads[i] = std::jthread([this] {
                WorkerThread();
            });
        }
    }

    template<typename Task>
    void Submit(Task&& task) {
        std::unique_lock<std::mutex> lock(mutex);
        conditionVariable.wait(lock, [this] {
            return tasks.size() < maxTasks;
        });

        tasks.emplace_back(std::forward<Task>(task));
        conditionVariable.notify_one();
    }

    void Shutdown() {
        {
            std::lock_guard<std::mutex> lock(mutex);
            shuttingDown = true;
        }

        conditionVariable.notify_all();

        for (auto& thread : threads) {
            if (thread.get_id() != std::jthread::id()) {
                thread.join();
            }
        }
    }

private:
    void WorkerThread() {
        while (true) {
            std::unique_lock<std::mutex> lock(mutex);

            conditionVariable.wait(lock, [this] {
                return shuttingDown || !tasks.empty();
            });

            if (shuttingDown) {
                return;
            }

            auto task = std::move(tasks.front());
            tasks.pop_front();

            lock.unlock();

            // Execute the task
            task();

            // Notify waiting threads that a task has completed
            conditionVariable.notify_all();
        }
    }

    size_t maxTasks = 10;
    std::vector<std::jthread> threads;
    std::deque<std::function<void()>> tasks;

    std::mutex mutex;
    std::condition_variable conditionVariable;
    bool shuttingDown = false;
};

int main() {
    ThreadPool pool(4);

    for (int i = 0; i < 20; ++i) {
        pool.Submit([i] {
            std::cout << "Task " << i << " executed by thread " << std::this_thread::get_id() << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(1));
        });
    }

    pool.Shutdown();

    return 0;
}

In this example, ThreadPool is a class that manages a pool of std::jthread instances. The Submit function is used to submit tasks to the thread pool. It limits the number of tasks by utilizing a maximum task count and a condition variable. The Shutdown function is called to gracefully terminate all threads once all tasks have been completed.

Conclusion

Throttling threads is an important technique in concurrent programming. With the introduction of std::jthread in C++20, handling threads has become more convenient and cleaner. By implementing a thread pool and using std::jthread to throttle the tasks, we can efficiently manage the number of threads executing concurrently.

Using std::jthread not only simplifies thread management but also improves code readability and reliability. It is worth exploring and leveraging the power of this new feature in your C++ projects.

#tech #threads #jthread #concurrency