C++ 2011: std::thread: exemple simple pour paralléliser une boucle?

C++ 2011 inclut de nouvelles fonctionnalités très cool, mais je ne peux pas trouver beaucoup d'exemple pour paralléliser une boucle. Donc ma question très naïve est: comment faire pour paralléliser une boucle simple (comme utiliser "omp parallel for") avec std::thread ? (Je recherche pour un exemple).

je vous Remercie beaucoup.

19
demandé sur Nicol Bolas 2012-05-29 05:32:24

6 réponses

std::thread n'est pas nécessairement destiné à paralyser les boucles. Il est censé être l'abstraction de faible niveau pour construire des constructions comme un parallel_for algorithme. Si vous voulez paralyser vos boucles, vous devez soit wirte un parallel_for algorithme vous-même ou utiliser des libraires existants qui offrent un parallisme basé sur les tâches.

l'exemple suivant montre comment on pourrait paralyser une boucle simple mais de l'autre côté montre aussi les inconvénients, comme l'équilibrage de charge manquant et la complexité pour une simple boucle.

  typedef std::vector<int> container;
  typedef container::iterator iter;

  container v(100, 1);

  auto worker = [] (iter begin, iter end) {
    for(auto it = begin; it != end; ++it) {
      *it *= 2;
    }
  };


  // serial
  worker(std::begin(v), std::end(v));

  std::cout << std::accumulate(std::begin(v), std::end(v), 0) << std::endl; // 200

  // parallel
  std::vector<std::thread> threads(8);
  const int grainsize = v.size() / 8;

  auto work_iter = std::begin(v);
  for(auto it = std::begin(threads); it != std::end(threads) - 1; ++it) {
    *it = std::thread(worker, work_iter, work_iter + grainsize);
    work_iter += grainsize;
  }
  threads.back() = std::thread(worker, work_iter, std::end(v));

  for(auto&& i : threads) {
    i.join();
  }

  std::cout << std::accumulate(std::begin(v), std::end(v), 0) << std::endl; // 400

utiliser une bibliothèque qui offre un parallel_for modèle, il peut être simplifié à l'

parallel_for(std::begin(v), std::end(v), worker);
29
répondu inf 2012-05-29 09:17:35

bien évidemment cela dépend de ce que fait votre boucle, comment vous choisissez de paralyser, et comment vous gérez la durée de vie des threads.

je suis en train de lire le livre de la std C++11 filetage de la bibliothèque (qui est aussi l'un des coup de pouce.thread mainteneur et écrit Thread ) et je peux voir que "ça dépend".

maintenant pour vous donner une idée des bases en utilisant le nouveau filetage standard, je vous recommande de lire le livre comme il donne de nombreux exemples. Aussi, jetez un coup d'oeil à http://www.justsoftwaresolutions.co.uk/threading/ et https://stackoverflow.com/questions/415994/boost-thread-tutorials

4
répondu Klaim 2017-05-23 11:46:39

ne peut pas fournir une réponse spécifique C++11 puisque nous utilisons encore principalement pthreads. Mais, en tant que réponse agnostique au langage, vous parallélisez quelque chose en le configurant pour l'exécuter dans une fonction séparée (la fonction thread).

en d'autres termes, vous avez une fonction comme:

def processArraySegment (threadData):
    arrayAddr = threadData->arrayAddr
    startIdx  = threadData->startIdx
    endIdx    = threadData->endIdx

    for i = startIdx to endIdx:
        doSomethingWith (arrayAddr[i])

    exitThread()

et, dans votre code, vous pouvez traiter le tableau en deux morceaux:

int xyzzy[100]

threadData->arrayAddr = xyzzy
threadData->startIdx  = 0
threadData->endIdx    = 49
threadData->done      = false
tid1 = startThread (processArraySegment, threadData)

// caveat coder: see below.
threadData->arrayAddr = xyzzy
threadData->startIdx  = 50
threadData->endIdx    = 99
threadData->done      = false
tid2 = startThread (processArraySegment, threadData)

waitForThreadExit (tid1)
waitForThreadExit (tid2)

(en gardant à l'esprit la mise en garde que vous devez vous assurer que thread 1 a chargé les données dans son local de stockage avant le thread principal commence à le modifier pour le thread 2, éventuellement avec un mutex ou en utilisant un array de structures, un par fil).

en d'autres termes, il est rarement simple de modifier un for boucle de sorte qu'il fonctionne en parallèle, bien que ce serait agréable, quelque chose comme:

for {threads=10} ({i} = 0; {i} < ARR_SZ; {i}++)
    array[{i}] = array[{i}] + 1;

au lieu de cela, il faut un peu réorganiser votre code pour profiter des threads.

et, bien sûr, vous devez s'assurer qu'il fait sens pour que les données puissent être traitées en parallèle. Si vous définissez chaque élément du tableau à l'élément précédent plus 1, Aucune quantité de traitement parallèle ne vous aidera, simplement parce que vous devez attendre que l'élément précédent soit modifié en premier.

cet exemple particulier ci-dessus utilise simplement un argument passé à la fonction thread pour spécifier quelle partie du tableau il doit traiter. La fonction thread elle-même contient la boucle pour faire le travail.

3
répondu paxdiablo 2012-05-29 02:08:39

en utilisant classe vous pouvez le faire aussi:

Range based loop (read and write)
pforeach(auto &val, container) { 
  val = sin(val); 
};

Index based for-loop
auto new_container = container;
pfor(size_t i, 0, container.size()) { 
  new_container[i] = sin(container[i]); 
};
3
répondu Viktor Sehr 2015-02-11 08:22:22

AFAIK la façon la plus simple de paralléliser une boucle, si vous êtes sûr qu'il n'y a pas d'accès simultané possible, est D'utiliser OpenMP.

il est supporté par tous les principaux compilateurs sauf LLVM (en août 2013).

Exemple :

for(int i = 0; i < n; ++i)
{
   tab[i] *= 2;
   tab2[i] /= 2;
   tab3[i] += tab[i] - tab2[i];
}

Ce serait parallélisée très facilement comme ceci :

#pragma omp parallel for
for(int i = 0; i < n; ++i)
{
   tab[i] *= 2;
   tab2[i] /= 2;
   tab3[i] += tab[i] - tab2[i];
}

cependant, sachez que ce n'est efficace qu'avec un grand nombre de valeurs.

si vous utilisez g++, une autre façon de faire serait d'utiliser un lambda et un for_each, et d'utiliser les extensions parallèles gnu (qui peuvent utiliser OpenMP en arrière-plan):

__gnu_parallel::for_each(std::begin(tab), std::end(tab), [&] () 
{
    stuff_of_your_loop();
});

cependant, for_each est principalement pensé pour les tableaux, vecteurs, etc... Mais vous pouvez "tricher" si vous voulez seulement itérer à travers une gamme en créant un Range classe begin et end méthode qui consiste principalement à incrémenter un int.

notez que pour les boucles simples qui font des trucs mathématiques, les algorithmes de #include <numeric> et #include <algorithm> tous les être mis en parallèle avec G++.

1
répondu Jean-Michaël Celerier 2013-08-06 06:10:02

permet de Définir une macro en utilisant std::thread et expression lambda:

#ifndef PARALLEL_FOR
#define PARALLEL_FOR(INT_LOOP_BEGIN_INCLUSIVE, INT_LOOP_END_EXCLUSIVE,I,O)          \                                                               \
    {                                                                               \
        int LOOP_LIMIT=INT_LOOP_END_EXCLUSIVE-INT_LOOP_BEGIN_INCLUSIVE;             \
        std::thread threads[LOOP_LIMIT]; auto fParallelLoop=[&](int I){ O; };       \
        for(int i=0; i<LOOP_LIMIT; i++)                                             \
        {                                                                           \
            threads[i]=std::thread(fParallelLoop,i+INT_LOOP_BEGIN_INCLUSIVE);       \
        }                                                                           \
        for(int i=0; i<LOOP_LIMIT; i++)                                             \
        {                                                                           \
            threads[i].join();                                                      \
        }                                                                           \
    }                                                                               \
#endif

utilisation:

int aaa=0;
PARALLEL_FOR(0,90,i,
{
    aaa+=i;
});

sa moche mais ça fonctionne.

0
répondu huseyin tugrul buyukisik 2017-07-15 10:51:13