"Detailed explanation and application of thread pool"

Introduction

Thread pool is a multi-threading technology used to effectively utilize the multi-core processor of the computer to improve the performance and efficiency of the program. The thread pool can manage and schedule multiple threads and allocate tasks to idle threads for execution, thereby avoiding the overhead of repeatedly creating and destroying threads, and avoiding the waste of system resources caused by too many threads.
The thread pool consists of a thread pool manager, worker threads and task queues. Under the control of the manager, multiple tasks are assigned to worker threads for processing, thereby improving the performance and efficiency of the program.
CPP thread pool is an advanced technology for realizing multi-threaded programming, which can improve the running efficiency of programs in high concurrency environments.

consider

If all tasks are executed infinitely, the limited number of threads in the thread pool will prevent tasks from being processed in time, thereby reducing the response speed and performance of the program.
In this case, you can consider increasing the number of threads in the thread pool to increase the concurrent processing capabilities of the program. In addition, special processing measures can be adopted for infinitely executed tasks, such as scheduled interruption or batch execution, to avoid tasks occupying thread resources for a long time.
It should be noted that although increasing the number of threads in the thread pool can improve the concurrent processing capabilities of the program, it will also cause a certain consumption of system resources. Therefore, when setting the thread pool size, you need to comprehensively consider the system's available resources and actual business needs to achieve optimal performance and efficiency.

Complete code details

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <functional>

class ThreadPool {
    
    
public:
    ThreadPool(size_t threadCount) : m_stop(false) {
    
    
        for (size_t i = 0; i < threadCount; ++i) {
    
    
            m_threads.emplace_back([this]() {
    
     // 利用 lambda 表达式创建线程
                while(true) {
    
    
                    std::unique_lock<std::mutex> lock(m_mutex);
                    m_cv.wait(lock, [this](){
    
     return m_stop || !m_tasks.empty(); }); // 等待任务或终止信号
                    if (m_stop && m_tasks.empty()) return;`在这里插入代码片`

                    std::function<void()> task = std::move(m_tasks.front()); // 取出任务
                    m_tasks.pop();
                    lock.unlock();
                    
                    task(); // 执行任务
                }
            });
        }
    }
    
    ~ThreadPool() {
    
    
        {
    
    
            std::unique_lock<std::mutex> lock(m_mutex);
            m_stop = true;
        }
        m_cv.notify_all();

        for (auto& thread : m_threads) {
    
    
            thread.join();
        }
    }

    template<class F, class... Args>
    void addTask(F&& f, Args&&... args) {
    
    
        std::function<void()> task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
        {
    
    
            std::unique_lock<std::mutex> lock(m_mutex);
            m_tasks.emplace(std::move(task));
        }
        m_cv.notify_one();
    }
    private:
    std::vector<std::thread> m_threads;
    std::queue<std::function<void()>> m_tasks;
    std::mutex m_mutex;
    std::condition_variable m_cv;
    bool m_stop;
    }

This code implements a simple thread pool with the following characteristics:
● The number of threads in the thread pool is specified by the constructor parameter;
● Supports adding tasks, and tasks are callable objects;
● The thread pool will wait for all tasks to be executed when it is destructed Complete and stop all threads;
● The thread pool supports multiple threads to concurrently call the addTask method, and the thread pool uses mutexes and condition variables to achieve thread synchronization.
Usage example:

void foo(int n) {
    
    
    std::cout << "Task " << n << " is running in thread " << std::this_thread::get_id() << std::endl;
}

int main() {
    
    
    ThreadPool pool(4);
    for (int i = 0; i < 10; ++i) {
    
    
        pool.addTask(foo, i);
    }
    return 0;
}

This example creates a thread pool and adds 10 tasks, each task is to print a piece of text. It can be seen that different tasks are assigned to different threads for execution.

Explain in detail

Thread pool is a commonly used multi-threaded programming model. It allocates multiple tasks to a fixed number of threads for execution, avoiding the overhead of frequent thread creation and destruction, and can also greatly improve the performance and efficiency of the program.

Thread pool constructor

In the constructor, we need to specify the number of threads in the thread pool. We use variadic templates in C++11 to support different types of callable objects. In the constructor, we initialize the thread array and use lambda expressions to create execution functions for each thread. The while loop in the lambda expression will always wait for a task to be executed in the task queue, and will also wait for a signal whether the thread pool has been terminated.

ThreadPool(size_t threadCount) : m_stop(false) {
    
    
    for (size_t i = 0; i < threadCount; ++i) {
    
    
        m_threads.emplace_back([this]() {
    
     // 利用 lambda 表达式创建线程
            while(true) {
    
    
                std::unique_lock<std::mutex> lock(m_mutex);
                m_cv.wait(lock, [this](){
    
     return m_stop || !m_tasks.empty(); }); // 等待任务或终止信号
                if (m_stop && m_tasks.empty()) return;

                std::function<void()> task = std::move(m_tasks.front()); // 取出任务
                m_tasks.pop();
                lock.unlock();
                
                task(); // 执行任务
            }
        });
    }
}

Thread pool destructor

In the destructor, we need to call the join() method to wait for all threads to exit and release resources. At the same time, we also need to set the m_stop signal to true so that the execution function of the thread pool can exit when the task queue is empty.

~ThreadPool() {
    
    
    {
    
    
        std::unique_lock<std::mutex> lock(m_mutex);
        m_stop = true;
    }
    m_cv.notify_all();

    for (auto& thread : m_threads) {
    
    
        thread.join();
    }
}

Add task function

The addTask() function is a function that adds tasks to the thread pool. It uses variadic templates to support different types of callable objects. It first needs to convert the callable object into a function<void()> type task, then add the task to the task queue, and notify a waiting thread to execute the task.

1template<class F, class... Args>
2void addTask(F&& f, Args&&... args) {
    
    
3    std::function<void()> task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
4    {
    
    
5        std::unique_lock<std::mutex> lock(m_mutex);
6        m_tasks.emplace(std::move(task));
7    }
8    m_cv.notify_one();
9}

Task queue and synchronization mechanism

Task queue and synchronization mechanism are the core of thread pool implementation. In the task queue, we use the std::queue<std::function<void()>> type to store tasks, where std::function<void()> represents the type of any callable object. Since multiple threads will share the task queue, we need to use a mutex (m_mutex) and a condition variable (m_cv) to achieve thread synchronization.
1std::queue<std::function<void()>> m_tasks;
2std::mutex m_mutex;
3std::condition_variable m_cv;
In the execution function, we use the wait() method of the condition variable m_cv to wait for the task queue There are tasks to perform. When a new task is added or the thread pool is terminated, we use the notify_one() method to wake up the waiting thread. At the same time, when we execute a task, we need to remove the task from the task queue first to avoid multiple threads executing the same task at the same time.

std::unique_lock<std::mutex> lock(m_mutex);
m_cv.wait(lock, [this](){
    
     return m_stop || !m_tasks.empty(); }); // 等待任务或终止信号
if (m_stop && m_tasks.empty()) return;

A wrapper for callable objects

When adding tasks, we need to wrap the callable object so that different types of callable objects can be converted into function<void()> type tasks. Here we use the std::bind() method to implement the binding of callable objects, and use std::forward(f) and std::forward(args)... to forward parameters.

template<class F, class... Args>
void addTask(F&& f, Args&&... args) {
    
    
    std::function<void()> task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
    {
    
    
        std::unique_lock<std::mutex> lock(m_mutex);
        m_tasks.emplace(std::move(task));
    }
    m_cv.notify_one();
}

Summarize

The above code implements a simple thread pool, which can help developers make better use of the computer's multi-core CPU to improve program performance and efficiency. The implementation of the thread pool needs to pay attention to the issue of thread synchronization. Thread synchronization can be easily achieved by using std::mutex and std::condition_variable. At the same time, pay attention to waiting for all tasks to be completed in the destructor to avoid resource leaks.

Guess you like

Origin blog.csdn.net/yiyu20180729/article/details/130730565