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.
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);
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
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.
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]);
};
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++.
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.