web-dev-qa-db-fra.com

Regroupement de threads en C ++ 11

Questions pertinentes :

À propos de C++ 11:

À propos de Boost:


Comment puis-je obtenir un groupe de threads pour envoyer des tâches à , sans les créer et les supprimer encore et encore? Cela signifie que les threads persistants doivent être resynchronisés sans être joints.


J'ai un code qui ressemble à ceci:

namespace {
  std::vector<std::thread> workers;

  int total = 4;
  int arr[4] = {0};

  void each_thread_does(int i) {
    arr[i] += 2;
  }
}

int main(int argc, char *argv[]) {
  for (int i = 0; i < 8; ++i) { // for 8 iterations,
    for (int j = 0; j < 4; ++j) {
      workers.Push_back(std::thread(each_thread_does, j));
    }
    for (std::thread &t: workers) {
      if (t.joinable()) {
        t.join();
      }
    }
    arr[4] = std::min_element(arr, arr+4);
  }
  return 0;
}

Au lieu de créer et de joindre des threads à chaque itération, je préférerais envoyer des tâches à mes threads de travail à chaque itération et ne les créer qu'une seule fois.

107
Yktula

Vous pouvez utiliser la bibliothèque de pools de threads C++, https://github.com/vit-vit/ctpl .

Ensuite, le code que vous avez écrit peut être remplacé par le suivant

#include <ctpl.h>  // or <ctpl_stl.h> if ou do not have Boost library

int main (int argc, char *argv[]) {
    ctpl::thread_pool p(2 /* two threads in the pool */);
    int arr[4] = {0};
    std::vector<std::future<void>> results(4);
    for (int i = 0; i < 8; ++i) { // for 8 iterations,
        for (int j = 0; j < 4; ++j) {
            results[j] = p.Push([&arr, j](int){ arr[j] +=2; });
        }
        for (int j = 0; j < 4; ++j) {
            results[j].get();
        }
        arr[4] = std::min_element(arr, arr + 4);
    }
}

Vous obtiendrez le nombre souhaité de threads sans créer ni les supprimer encore et encore lors des itérations.

70
vit-vit

Un pool de threads signifie que tous vos threads fonctionnent tout le temps - autrement dit, la fonction thread ne revient jamais. Pour donner un sens aux threads, vous devez concevoir un système de communication inter-thread, à la fois pour indiquer au thread qu'il y a quelque chose à faire et pour communiquer les données de travail réelles.

En règle générale, cela implique une sorte de structure de données concurrente, et chaque thread dormirait probablement sur une sorte de variable de condition, qui serait notifiée lorsqu'il y a du travail à faire. À la réception de la notification, un ou plusieurs threads se réveillent, récupèrent une tâche de la structure de données simultanée, la traitent et stockent le résultat de manière analogue.

Le fil vérifie ensuite s'il y a encore du travail à faire, et sinon, se rendort.

Le résultat est que vous devez concevoir tout cela vous-même, car il n'y a pas de notion naturelle de "travail" universellement applicable. C'est beaucoup de travail et il y a quelques problèmes subtils que vous devez résoudre. (Vous pouvez programmer dans Go si vous aimez un système qui s'occupe de la gestion des threads pour vous dans les coulisses.)

59
Kerrek SB

Ceci est copié de ma réponse à un autre post très similaire, espérons que cela peut aider:

1) Commencez avec le nombre maximal de threads qu'un système peut prendre en charge:

int Num_Threads =  thread::hardware_concurrency();

2) Pour une implémentation efficace de pool de threads, une fois que les threads sont créés conformément à Num_Threads, il est préférable de ne pas en créer de nouveaux, ni de détruire les anciens (en se joignant). Il y aura une pénalité de performance, peut même rendre votre application plus lente que la version série.

Chaque thread C++ 11 doit fonctionner dans sa fonction avec une boucle infinie, dans l'attente constante de nouvelles tâches à saisir et à exécuter.

Voici comment attacher une telle fonction au pool de threads:

int Num_Threads = thread::hardware_concurrency();
vector<thread> Pool;
for(int ii = 0; ii < Num_Threads; ii++)
{  Pool.Push_back(thread(Infinite_loop_function));}

3) La fonction infinite_loop_function

Ceci est une boucle "while (true)" en attente de la file d'attente des tâches

void The_Pool:: Infinite_loop_function()
{
    while(true)
    {
        {
            unique_lock<mutex> lock(Queue_Mutex);

            condition.wait(lock, []{return !Queue.empty() || therminate_pool});
            Job = Queue.front();
            Queue.pop();
        }
        Job(); // function<void()> type
    }
};

4) Créer une fonction pour ajouter un travail à votre file d'attente

void The_Pool:: Add_Job(function<void()> New_Job)
{
    {
        unique_lock<mutex> lock(Queue_Mutex);
        Queue.Push(New_Job);
    }
    condition.notify_one();
}

5) Lier une fonction arbitraire à votre file d'attente

Pool_Obj.Add_Job(std::bind(&Some_Class::Some_Method, &Some_object));

Une fois que vous avez intégré ces ingrédients, vous avez votre propre pool de threading dynamique. Ces threads sont toujours exécutés, en attente du travail à effectuer.

Je m'excuse s'il y a des erreurs de syntaxe, j'ai tapé ce code et j'ai une mauvaise mémoire. Désolé de ne pouvoir vous fournir le code complet du pool de threads, cela violerait l'intégrité de mon travail.

Edit: pour terminer le pool, appelez la méthode shutdown ():

XXXX::shutdown(){
{   unique_lock<mutex> lock(threadpool_mutex);
    terminate_pool = true;} // use this flag in condition.wait

condition.notify_all(); // wake up all threads.

// Join all threads.
for(std::thread &every_thread : thread_vector)
{   every_thread.join();}

thread_vector.empty();  
stopped = true; // use this flag in destructor, if not set, call shutdown() 

}

54
PhD AP EcE

Un pool de threads est au cœur d'un ensemble de threads tous liés à une fonction fonctionnant comme une boucle d'événement. Ces threads attendront sans fin qu'une tâche soit exécutée ou leur propre terminaison.

Le travail de pool de threads consiste à fournir une interface permettant de soumettre des travaux, de définir (et éventuellement de modifier) ​​la stratégie d'exécution de ces travaux (règles de planification, instanciation de thread, taille du pool) et de surveiller le statut des threads et des ressources associées.

Donc, pour un pool polyvalent, il faut commencer par définir ce qu'est une tâche, comment elle est lancée, interrompue, quel est le résultat (voir la notion de promesse et de futur pour cette question), quels types d'événements les threads devront répondre comment ils vont les gérer, comment ces événements doivent être distingués de ceux traités par les tâches. Comme vous pouvez le constater, cela peut devenir assez compliqué et imposer des restrictions au fonctionnement des threads, car la solution devient de plus en plus complexe.

L'outil actuel de gestion des événements est plutôt nu-osseux (*): primitives telles que mutex, variables de condition et quelques abstractions en plus (verrous, barrières). Mais dans certains cas, ces abstrations peuvent s'avérer inaptes (voir ce connexe question ), et il faut revenir à utiliser les primitives.

D'autres problèmes doivent également être gérés:

  • signal
  • i/o
  • matériel (affinité du processeur, configuration hétérogène)

Comment cela se jouerait-il dans votre contexte?

Cette réponse à une question similaire pointe vers une implémentation existante destinée à boost et à la stl.

J'ai proposé une implémentation très grossière d'un threadpool pour une autre question, qui ne résout pas beaucoup des problèmes décrits ci-dessus. Vous voudrez peut-être construire dessus. Vous voudrez peut-être aussi jeter un coup d’œil sur les frameworks existants dans d’autres langues pour trouver l’inspiration.


(*) Je ne vois pas cela comme un problème, bien au contraire. Je pense que c'est l'esprit même de C++ hérité de C.

18
didierc

Quelque chose comme cela pourrait aider (tiré d'une application qui fonctionne).

#include <memory>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

struct thread_pool {
  typedef std::unique_ptr<boost::asio::io_service::work> asio_worker;

  thread_pool(int threads) :service(), service_worker(new asio_worker::element_type(service)) {
    for (int i = 0; i < threads; ++i) {
      auto worker = [this] { return service.run(); };
      grp.add_thread(new boost::thread(worker));
    }
  }

  template<class F>
  void enqueue(F f) {
    service.post(f);
  }

  ~thread_pool() {
    service_worker.reset();
    grp.join_all();
    service.stop();
  }

private:
  boost::asio::io_service service;
  asio_worker service_worker;
  boost::thread_group grp;
};

Vous pouvez l'utiliser comme ceci:

thread_pool pool(2);

pool.enqueue([] {
  std::cout << "Hello from Task 1\n";
});

pool.enqueue([] {
  std::cout << "Hello from Task 2\n";
});

N'oubliez pas que réinventer un mécanisme de mise en file d'attente asynchrone efficace n'est pas trivial.

Boost :: asio :: io_service est une implémentation très efficace ou constitue en fait une collection de wrappers spécifiques à la plate-forme (par exemple, elle encapsule les ports d’achèvement des E/S sous Windows).

3
rustyx

Edit: Cela nécessite maintenant C++ 17 et des concepts. (Au 12/09/16, seul g ++ 6.0+ suffit.)

La déduction de modèle est beaucoup plus précise à cause de cela, donc il vaut la peine de chercher un compilateur plus récent. Je n'ai pas encore trouvé de fonction nécessitant des arguments de modèles explicites.

Il prend aussi maintenant n'importe quel objet appelable approprié (et est toujours statiquement typé !!!).

Il inclut également désormais un pool de threads de priorité de threads verts facultatif utilisant la même API. Cette classe est seulement POSIX, cependant. Il utilise l'API ucontext_t pour le basculement de tâches dans l'espace utilisateur.


J'ai créé une bibliothèque simple pour cela. Un exemple d'utilisation est donné ci-dessous. (Je réponds à cela parce que c'était l'une des choses que j'ai trouvées avant de décider qu'il était nécessaire de l'écrire moi-même.)

bool is_prime(int n){
  // Determine if n is prime.
}

int main(){
  thread_pool pool(8); // 8 threads

  list<future<bool>> results;
  for(int n = 2;n < 10000;n++){
    // Submit a job to the pool.
    results.emplace_back(pool.async(is_prime, n));
  }

  int n = 2;
  for(auto i = results.begin();i != results.end();i++, n++){
    // i is an iterator pointing to a future representing the result of is_prime(n)
    cout << n << " ";
    bool prime = i->get(); // Wait for the task is_prime(n) to finish and get the result.
    if(prime)
      cout << "is prime";
    else
      cout << "is not prime";
    cout << endl;
  }  
}

Vous pouvez transmettre async à n’importe quelle fonction avec une valeur de retour (ou vide) et n’importe quel argument (ou aucun) et elle renverra le std::future correspondant. Pour obtenir le résultat (ou simplement attendre la fin d'une tâche), appelez get() ultérieurement.

Voici le github: https://github.com/Tyler-Hardin/thread_pool .

2
Tyler

Ceci est une autre implémentation de pool de threads qui est très simple, facile à comprendre et à utiliser, utilise uniquement la bibliothèque standard C++ 11, et peut être consultée ou modifiée pour vos utilisations, devrait être un bon débutant si vous souhaitez vous initier à l'utilisation de thread piscines:

https://github.com/progschj/ThreadPool

2
Halcyon
Follwoing [PhD EcE](https://stackoverflow.com/users/3818417/phd-ece) suggestion, I implemented the thread pool:

function_pool.h

#pragma once
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <cassert>

class Function_pool
{

private:
    std::queue<std::function<void()>> m_function_queue;
    std::mutex m_lock;
    std::condition_variable m_data_condition;
    std::atomic<bool> m_accept_functions;

public:

    Function_pool();
    ~Function_pool();
    void Push(std::function<void()> func);
    void done();
    void infinite_loop_func();
};

function_pool.cpp

#include "function_pool.h"

Function_pool::Function_pool() : m_function_queue(), m_lock(), m_data_condition(), m_accept_functions(true)
{
}

Function_pool::~Function_pool()
{
}

void Function_pool::Push(std::function<void()> func)
{
    std::unique_lock<std::mutex> lock(m_lock);
    m_function_queue.Push(func);
    // when we send the notification immediately, the consumer will try to get the lock , so unlock asap
    lock.unlock();
    m_data_condition.notify_one();
}

void Function_pool::done()
{
    std::unique_lock<std::mutex> lock(m_lock);
    m_accept_functions = false;
    lock.unlock();
    // when we send the notification immediately, the consumer will try to get the lock , so unlock asap
    m_data_condition.notify_all();
    //notify all waiting threads.
}

void Function_pool::infinite_loop_func()
{
    std::function<void()> func;
    while (true)
    {
        {
            std::unique_lock<std::mutex> lock(m_lock);
            m_data_condition.wait(lock, [this]() {return !m_function_queue.empty() || !m_accept_functions; });
            if (!m_accept_functions && m_function_queue.empty())
            {
                //lock will be release automatically.
                //finish the thread loop and let it join in the main thread.
                return;
            }
            func = m_function_queue.front();
            m_function_queue.pop();
            //release the lock
        }
        func();
    }
}

main.cpp

#include "function_pool.h"
#include <string>
#include <iostream>
#include <mutex>
#include <functional>
#include <thread>
#include <vector>

Function_pool func_pool;

class quit_worker_exception : public std::exception {};

void example_function()
{
    std::cout << "bla" << std::endl;
}

int main()
{
    std::cout << "stating operation" << std::endl;
    int num_threads = std::thread::hardware_concurrency();
    std::cout << "number of threads = " << num_threads << std::endl;
    std::vector<std::thread> thread_pool;
    for (int i = 0; i < num_threads; i++)
    {
        thread_pool.Push_back(std::thread(&Function_pool::infinite_loop_func, &func_pool));
    }

    //here we should send our functions
    for (int i = 0; i < 50; i++)
    {
        func_pool.Push(example_function);
    }
    func_pool.done();
    for (unsigned int i = 0; i < thread_pool.size(); i++)
    {
        thread_pool.at(i).join();
    }
}
2
pio

Un pool de threads sans dépendance en dehors de STL est tout à fait possible. J'ai récemment écrit un petit bibliothèque d'en-tête de pool de threads uniquement pour résoudre exactement le même problème. Il prend en charge le redimensionnement dynamique du pool (modification du nombre de travailleurs à l'exécution), l'attente, l'arrêt, la pause, la reprise, etc. J'espère que tu trouves cela utile.

0
cantordust

Vous pouvez utiliser thread_pool depuis la librairie boost:

void my_task(){...}

int main(){
    int threadNumbers = thread::hardware_concurrency();
    boost::asio::thread_pool pool(threadNumbers);

    // Submit a function to the pool.
    boost::asio::post(pool, my_task);

    // Submit a lambda object to the pool.
    boost::asio::post(pool, []() {
      ...
    });
}

Vous pouvez également utiliser threadpool de la communauté open source:

void first_task() {...}    
void second_task() {...}

int main(){
    int threadNumbers = thread::hardware_concurrency();
    pool tp(threadNumbers);

    // Add some tasks to the pool.
    tp.schedule(&first_task);
    tp.schedule(&second_task);
}
0
Amir Forsati