C++
Concorrenza con OpenMP
Ricerca…
introduzione
Questo argomento copre le basi della concorrenza in C ++ usando OpenMP. OpenMP è documentato in modo più dettagliato nel tag OpenMP .
Il parallelismo o la concorrenza implica l'esecuzione del codice allo stesso tempo.
Osservazioni
OpenMP non richiede intestazioni o librerie speciali in quanto è una funzionalità incorporata del compilatore. Tuttavia, se si utilizzano funzioni API OpenMP come omp_get_thread_num()
, sarà necessario includere omp.h
e la relativa libreria.
Le istruzioni pragma
OpenMP vengono ignorate quando l'opzione OpenMP non è abilitata durante la compilazione. Si consiglia di fare riferimento all'opzione del compilatore nel manuale del compilatore.
- GCC utilizza
-fopenmp
- Clang usa
-fopenmp
- MSVC utilizza
/openmp
OpenMP: sezioni parallele
Questo esempio illustra le basi dell'esecuzione di sezioni di codice in parallelo.
Poichè OpenMP è una funzionalità incorporata del compilatore, funziona su qualsiasi compilatore supportato senza includere alcuna libreria. Si consiglia di includere omp.h
se si desidera utilizzare una delle funzioni API openMP.
Codice di esempio
std::cout << "begin ";
// This pragma statement hints the compiler that the
// contents within the { } are to be executed in as
// parallel sections using openMP, the compiler will
// generate this chunk of code for parallel execution
#pragma omp parallel sections
{
// This pragma statement hints the compiler that
// this is a section that can be executed in parallel
// with other section, a single section will be executed
// by a single thread.
// Note that it is "section" as opposed to "sections" above
#pragma omp section
{
std::cout << "hello " << std::endl;
/** Do something **/
}
#pragma omp section
{
std::cout << "world " << std::endl;
/** Do something **/
}
}
// This line will not be executed until all the
// sections defined above terminates
std::cout << "end" << std::endl;
Uscite
Questo esempio produce 2 possibili uscite e dipende dal sistema operativo e dall'hardware. L'output illustra anche un problema di condizione di competizione che si verificherebbe da tale implementazione.
USCITA A | USCITA B |
---|---|
inizio ciao fine del mondo | inizio mondo ciao fine |
OpenMP: sezioni parallele
Questo esempio mostra come eseguire blocchi di codice in parallelo
std::cout << "begin ";
// Start of parallel sections
#pragma omp parallel sections
{
// Execute these sections in parallel
#pragma omp section
{
... do something ...
std::cout << "hello ";
}
#pragma omp section
{
... do something ...
std::cout << "world ";
}
#pragma omp section
{
... do something ...
std::cout << "forever ";
}
}
// end of parallel sections
std::cout << "end";
Produzione
- inizio ciao mondo per sempre fine
- inizio mondo ciao alla fine per sempre
- inizio ciao per sempre alla fine del mondo
- inizia per sempre ciao fine del mondo
Poiché l'ordine di esecuzione non è garantito, è possibile osservare uno qualsiasi dei risultati sopra riportati.
OpenMP: Parallel For Loop
Questo esempio mostra come dividere un loop in parti uguali ed eseguirli in parallelo.
// Splits element vector into element.size() / Thread Qty
// and allocate that range for each thread.
#pragma omp parallel for
for (size_t i = 0; i < element.size(); ++i)
element[i] = ...
// Example Allocation (100 element per thread)
// Thread 1 : 0 ~ 99
// Thread 2 : 100 ~ 199
// Thread 2 : 200 ~ 299
// ...
// Continue process
// Only when all threads completed their allocated
// loop job
...
* Prestare particolare attenzione a non modificare le dimensioni del vettore utilizzato in parallelo per i cicli in quanto gli indici di intervallo assegnati non si aggiornano automaticamente .
OpenMP: Parallel Gathering / Reduction
Questo esempio illustra un concetto per eseguire la riduzione o la raccolta utilizzando std::vector
e OpenMP.
Supposto che abbiamo uno scenario in cui vogliamo più thread per aiutarci a generare un sacco di cose, int
è qui usato per semplicità e può essere sostituito con altri tipi di dati.
Ciò è particolarmente utile quando è necessario unire i risultati degli slave per evitare errori di segmentazione o violazioni di accesso alla memoria e non si desidera utilizzare librerie o librerie contenitore di sincronizzazione personalizzate.
// The Master vector
// We want a vector of results gathered from slave threads
std::vector<int> Master;
// Hint the compiler to parallelize this { } of code
// with all available threads (usually the same as logical processor qty)
#pragma omp parallel
{
// In this area, you can write any code you want for each
// slave thread, in this case a vector to hold each of their results
// We don't have to worry about how many threads were spawn or if we need
// to repeat this declaration or not.
std::vector<int> Slave;
// Tell the compiler to use all threads allocated for this parallel region
// to perform this loop in parts. Actual load appx = 1000000 / Thread Qty
// The nowait keyword tells the compiler that the slave threads don't
// have to wait for all other slaves to finish this for loop job
#pragma omp for nowait
for (size_t i = 0; i < 1000000; ++i
{
/* Do something */
....
Slave.push_back(...);
}
// Slaves that finished their part of the job
// will perform this thread by thread one at a time
// critical section ensures that only 0 or 1 thread performs
// the { } at any time
#pragma omp critical
{
// Merge slave into master
// use move iterators instead, avoid copy unless
// you want to use it for something else after this section
Master.insert(Master.end(),
std::make_move_iterator(Slave.begin()),
std::make_move_iterator(Slave.end()));
}
}
// Have fun with Master vector
...