Maison  >  Article  >  développement back-end  >  Techniques d'optimisation multithread en C++

Techniques d'optimisation multithread en C++

王林
王林original
2023-08-22 12:53:121021parcourir

Techniques doptimisation multithread en C++

Avec le développement de la technologie informatique et l'amélioration des performances du matériel, la technologie multi-threading est devenue une compétence essentielle pour la programmation moderne. C++ est un langage de programmation classique qui fournit également de nombreuses technologies multithreading puissantes. Cet article présentera quelques techniques d'optimisation multi-thread en C++ pour aider les lecteurs à mieux appliquer la technologie multi-threading.

1. Utilisez std::thread

C++11 a introduit std::thread, intégrant la technologie multi-thread directement dans la bibliothèque standard. Créer un nouveau thread à l'aide de std::thread est très simple, il suffit de passer un pointeur de fonction. Par exemple :

#include <thread>
#include <iostream>

void hello()
{
    std::cout << "Hello World!";
}

int main()
{
    std::thread t(hello);
    t.join();
    return 0;
}

Le code ci-dessus crée un nouveau thread t, exécute la fonction hello et attend que le thread t se termine. Notez que la création et la destruction de threads nécessitent une certaine surcharge, donc std::thread doit être utilisé de manière rationnelle.

2. Utilisez std::async

std::async est une autre technologie multi-threading pratique. Elle peut exécuter une fonction de manière asynchrone et renvoyer un objet std::future. Utilisez std::async pour gérer plus facilement l'exécution de tâches asynchrones et obtenir des résultats. Par exemple :

#include <future>
#include <iostream>

int add(int a, int b)
{
    return a + b;
}

int main()
{
    auto async_result = std::async(add, 1, 2);
    std::cout << async_result.get();
    return 0;
}

Le code ci-dessus appelle la fonction add pour calculer 1+2 de manière asynchrone et utilise les objets std::future pour gérer l'acquisition des résultats de calcul. Il convient de noter que std::async utilise la stratégie std::launch::async par défaut et exécutera des fonctions dans un nouveau thread. Si vous souhaitez utiliser la stratégie std::launch::deferred, vous devez la spécifier manuellement. Cependant, l'utilisation de la stratégie std::launch::deferred entraînera l'exécution de la fonction uniquement lorsque std::future::get() sera appelée, le choix doit donc être fait au cas par cas.

3. Utilisez std::condition_variable

Dans la programmation multi-thread, la communication et la synchronisation doivent être effectuées entre les threads, et std::condition_variable peut très bien atteindre cet objectif. L'utilisation de std::condition_variable permet à un thread d'attendre qu'une certaine condition d'un autre thread soit vraie, réalisant ainsi la synchronisation entre les threads. Par exemple :

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

std::mutex mutex;
std::condition_variable cv;
bool ready = false;

void producer()
{
    std::unique_lock<std::mutex> lock(mutex);
    // wait for the condition to become true
    cv.wait(lock, [] { return ready; });
    std::cout << "Producer done." << std::endl;
}

void consumer()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    ready = true;
    std::cout << "Consumer done." << std::endl;
    cv.notify_one();
}

int main()
{
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join();
    t2.join();
    return 0;
}

Le code ci-dessus crée deux threads t1 et t2, où t1 attend qu'une variable de condition prête devienne vraie, tandis que t2 définit la variable de condition sur vrai après avoir attendu 1 seconde et informe t1. Il convient de noter que std::condition_variable doit être utilisé conjointement avec std::mutex pour empêcher plusieurs threads d'accéder aux variables de condition en même temps.

4. Utiliser le pool de threads

Dans le cas d'un grand nombre de tâches à court terme qui doivent être créées et exécutées, les pools de threads sont souvent utilisés pour améliorer les performances du programme. Le pool de threads maintient un certain nombre de threads et gère l'allocation et l'exécution des tâches. L'utilisation d'un pool de threads peut éviter la surcharge supplémentaire liée à la création et à la destruction fréquentes de threads, tout en tirant pleinement parti des processeurs multicœurs. Par exemple :

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

class ThreadPool
{
public:
    ThreadPool(std::size_t numThreads = std::thread::hardware_concurrency())
    {
        for (std::size_t i = 0; i < numThreads; ++i)
        {
            pool.emplace_back([this] {
                while (!stop)
                {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock{ mutex };
                        condition.wait(lock, [this] { return stop || !tasks.empty(); });
                        if (stop && tasks.empty()) return;
                        task = std::move(tasks.front());
                        tasks.pop();
                    }
                    task();
                }
            });
        }
    }

    ~ThreadPool()
    {
        {
            std::unique_lock<std::mutex> lock{ mutex };
            stop = true;
        }
        condition.notify_all();
        for (auto& worker : pool)
        {
            worker.join();
        }
    }

    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> future = task->get_future();
        {
            std::unique_lock<std::mutex> lock{ mutex };
            if (stop) throw std::runtime_error("enqueue on stopped ThreadPool");
            tasks.emplace([task](){ (*task)(); });
        }
        condition.notify_one();
        return future;
    }

private:
    std::vector<std::thread> pool;
    std::queue<std::function<void()>> tasks;
    std::mutex mutex;
    std::condition_variable condition;
    bool stop = false;
};

void hello()
{
    std::cout << "Hello World!" << std::endl;
}

int add(int a, int b)
{
    return a + b;
}

int main()
{
    {
        ThreadPool pool;
        auto f1 = pool.enqueue(hello);
        auto f2 = pool.enqueue(add, 1, 2);
        std::cout << f2.get() << std::endl;
    }
    return 0;
}

Le code ci-dessus définit une classe ThreadPool, qui contient plusieurs threads et une file d'attente de tâches. Le pool de threads continue de prendre des tâches de la file d'attente des tâches et de les exécuter jusqu'à ce que la file d'attente soit vide ou que le pool de threads s'arrête. Utilisez la méthode ThreadPool :: enqueue pour ajouter la tâche à la file d'attente des tâches et renvoyer un objet std :: future pour gérer les résultats de l'exécution de la tâche.

En général, C++ fournit une variété de technologies multithread pour aider les développeurs à tirer parti des performances des processeurs multicœurs et à gérer les threads et les tâches de manière plus flexible. Les développeurs doivent utiliser ces techniques de manière appropriée pour optimiser les performances du programme.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn