"Explication détaillée et application du pool de threads"

Introduction

Le pool de threads est une technologie multithread utilisée pour utiliser efficacement le processeur multicœur de l'ordinateur afin d'améliorer les performances et l'efficacité du programme. Le pool de threads peut gérer et planifier plusieurs threads et allouer des tâches aux threads inactifs pour exécution, évitant ainsi la surcharge liée à la création et à la destruction répétées de threads et évitant le gaspillage de ressources système causé par un trop grand nombre de threads.
Le pool de threads se compose d'un gestionnaire de pool de threads, de threads de travail et de files d'attente de tâches. Sous le contrôle du gestionnaire, plusieurs tâches sont assignées aux threads de travail pour traitement, améliorant ainsi les performances et l'efficacité du programme.
Le pool de threads CPP est une technologie avancée permettant de réaliser une programmation multithread, qui peut améliorer l'efficacité d'exécution des programmes dans des environnements à haute concurrence.

considérer

Si toutes les tâches sont exécutées à l'infini, le nombre limité de threads dans le pool de threads empêchera les tâches d'être traitées à temps, réduisant ainsi la vitesse de réponse et les performances du programme.
Dans ce cas, vous pouvez envisager d'augmenter le nombre de threads dans le pool de threads pour augmenter les capacités de traitement simultané du programme. De plus, des mesures de traitement spéciales peuvent être adoptées pour les tâches exécutées à l'infini, telles qu'une interruption planifiée ou une exécution par lots, afin d'éviter que les tâches n'occupent les ressources des threads pendant une longue période.
Il convient de noter que même si l'augmentation du nombre de threads dans le pool de threads peut améliorer les capacités de traitement simultané du programme, cela entraînera également une certaine consommation de ressources système. Par conséquent, lors de la définition de la taille du pool de threads, vous devez prendre en compte de manière exhaustive les ressources disponibles du système et les besoins réels de l'entreprise pour obtenir des performances et une efficacité optimales.

Détails complets du code

#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;
    }

Ce code implémente un pool de threads simple avec les caractéristiques suivantes :
● Le nombre de threads dans le pool de threads est spécifié par le paramètre constructeur ;
● Prend en charge l'ajout de tâches et les tâches sont des objets appelables ;
● Le pool de threads attendra que toutes les tâches soient exécuté lorsqu'il est détruit Terminer et arrêter tous les threads ;
● Le pool de threads prend en charge plusieurs threads pour appeler simultanément la méthode addTask, et le pool de threads utilise des mutex et des variables de condition pour réaliser la synchronisation des threads.
Exemple d'utilisation :

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;
}

Cet exemple crée un pool de threads et ajoute 10 tâches, chaque tâche consiste à imprimer un morceau de texte. On peut voir que différentes tâches sont assignées à différents threads pour exécution.

Expliquer en détail

Le pool de threads est un modèle de programmation multithread couramment utilisé.Il alloue plusieurs tâches à un nombre fixe de threads pour l'exécution, évitant ainsi la surcharge liée à la création et à la destruction fréquentes de threads, et peut également améliorer considérablement les performances et l'efficacité du programme.

Constructeur de pool de threads

Dans le constructeur, nous devons spécifier le nombre de threads dans le pool de threads. Nous utilisons des modèles variadiques en C++11 pour prendre en charge différents types d'objets appelables. Dans le constructeur, nous initialisons le tableau de threads et utilisons des expressions lambda pour créer des fonctions d'exécution pour chaque thread. La boucle while dans l'expression lambda attendra toujours qu'une tâche soit exécutée dans la file d'attente des tâches et attendra également un signal indiquant si le pool de threads est terminé.

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(); // 执行任务
            }
        });
    }
}

Destructeur de pool de threads

Dans le destructeur, nous devons appeler la méthode join() pour attendre que tous les threads se terminent et libèrent les ressources. Dans le même temps, nous devons également définir le signal m_stop sur true afin que la fonction d'exécution du pool de threads puisse se terminer lorsque la file d'attente des tâches est vide.

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

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

Ajouter une fonction de tâche

La fonction addTask() est une fonction qui ajoute des tâches au pool de threads. Il utilise des modèles variadiques pour prendre en charge différents types d'objets appelables. Il doit d'abord convertir l'objet appelable en une tâche de type function<void()>, puis ajouter la tâche à la file d'attente des tâches et notifier à un thread en attente d'exécuter la tâche.

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}

File d'attente des tâches et mécanisme de synchronisation

La file d'attente des tâches et le mécanisme de synchronisation sont au cœur de la mise en œuvre du pool de threads. Dans la file d'attente des tâches, nous utilisons le type std::queue<std::function<void()>> pour stocker les tâches, où std::function<void()> représente le type de tout objet appelable. Étant donné que plusieurs threads partageront la file d'attente des tâches, nous devons utiliser un mutex (m_mutex) et une variable de condition (m_cv) pour réaliser la synchronisation des threads.
1std::queue<std::function<void()>> m_tasks;
2std::mutex m_mutex;
3std::condition_variable m_cv;
Dans la fonction d'exécution, nous utilisons la méthode wait() de la variable de condition m_cv pour attendre le file d'attente des tâches Il y a des tâches à effectuer. Lorsqu'une nouvelle tâche est ajoutée ou que le pool de threads est terminé, nous utilisons la méthode notify_one() pour réveiller le thread en attente. Dans le même temps, lorsque nous exécutons une tâche, nous devons d'abord supprimer la tâche de la file d'attente des tâches pour éviter que plusieurs threads n'exécutent la même tâche en même temps.

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;

Un wrapper pour les objets appelables

Lors de l'ajout de tâches, nous devons envelopper l'objet appelable afin que différents types d'objets appelables puissent être convertis en tâches de type function<void()>. Ici, nous utilisons la méthode std::bind() pour implémenter la liaison d'objets appelables, et utilisons std::forward(f) et std::forward(args)... pour transférer les paramètres.

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();
}

Résumer

Le code ci-dessus implémente un pool de threads simple, qui peut aider les développeurs à mieux utiliser le processeur multicœur de l'ordinateur pour améliorer les performances et l'efficacité du programme. L'implémentation du pool de threads doit prêter attention à la question de la synchronisation des threads.La synchronisation des threads peut être facilement réalisée en utilisant std::mutex et std::condition_variable. Dans le même temps, faites attention à attendre que toutes les tâches soient terminées dans le destructeur pour éviter les fuites de ressources.

Je suppose que tu aimes

Origine blog.csdn.net/yiyu20180729/article/details/130730565
conseillé
Classement