Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Ниже приведены примеры конструкций, определенных в этом документе. Оператор, следующий за директивой, является составным, только если это необходимо, и не составная инструкция отступится от директивы, предшествующей ей.
A.1 Простой цикл параллельно
В следующем примере показано, как параллелизировать цикл с помощью параллельной директивы. Переменная итерации цикла является частной по умолчанию, поэтому не нужно явно указывать ее в частном предложении.
#pragma omp parallel for
for (i=1; i<n; i++)
b[i] = (a[i] + a[i-1]) / 2.0;
Условная компиляция A.2
В следующих примерах показано использование условной компиляции с помощью макроса OpenMP _OPENMP. При компиляции _OPENMP OpenMP макрос определяется.
# ifdef _OPENMP
printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif
Определенный оператор препроцессора позволяет тестировать несколько макросов в одной директиве.
# if defined(_OPENMP) && defined(VERBOSE)
printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif
Параллельные регионы A.3
Параллельная директива может использоваться в параллельных программах с грубой степенью детализации. В следующем примере каждый поток в параллельном регионе решает, какая часть глобального массива x будет работать на основе номера потока:
#pragma omp parallel shared(x, npoints) private(iam, np, ipoints)
{
iam = omp_get_thread_num();
np = omp_get_num_threads();
ipoints = npoints / np;
subdomain(x, iam, ipoints);
}
A.4 Предложение nowait
Если в параллельном регионе существует множество независимых циклов, можно использовать предложение nowait , чтобы избежать подразумеваемого барьера в конце for директивы, как показано ниже.
#pragma omp parallel
{
#pragma omp for nowait
for (i=1; i<n; i++)
b[i] = (a[i] + a[i-1]) / 2.0;
#pragma omp for nowait
for (i=0; i<m; i++)
y[i] = sqrt(z[i]);
}
A.5 Критической директивы
В следующем примере содержится несколько критически важных директив. В этом примере показана модель очередей, в которой задача отложена и работает. Чтобы защититься от многих потоков, отменяющих одну и ту же задачу, операция вывода должна находиться в critical разделе. Так как две очереди в этом примере независимы, они защищены critical директивами с различными именами, xaxis и yaxis.
#pragma omp parallel shared(x, y) private(x_next, y_next)
{
#pragma omp critical ( xaxis )
x_next = dequeue(x);
work(x_next);
#pragma omp critical ( yaxis )
y_next = dequeue(y);
work(y_next);
}
A.6 Предложение lastprivate
Правильное выполнение иногда зависит от значения, которое назначается переменной последней итерации цикла. Такие программы должны перечислять все такие переменные, как аргументы в предложение lastprivate , чтобы значения переменных были одинаковыми, как при последовательном выполнении цикла.
#pragma omp parallel
{
#pragma omp for lastprivate(i)
for (i=0; i<n-1; i++)
a[i] = b[i] + b[i+1];
}
a[i]=b[i];
В предыдущем примере значение i в конце параллельного региона будет равно n-1, как в последовательном случае.
A.7 Предложение сокращения
В следующем примере показано предложение сокращения :
#pragma omp parallel for private(i) shared(x, y, n) \
reduction(+: a, b)
for (i=0; i<n; i++) {
a = a + x[i];
b = b + y[i];
}
Разделы A.8 Parallel
В следующем примере (для раздела 2.4.2) функции xaxis, yaxis и zaxis можно выполнять одновременно. Первая section директива является необязательной. Все section директивы должны отображаться в лексической степени parallel sections конструкции.
#pragma omp parallel sections
{
#pragma omp section
xaxis();
#pragma omp section
yaxis();
#pragma omp section
zaxis();
}
Директивы A.9 Single
В следующем примере показана одна директива. В примере выводит сообщение о ходе выполнения только один поток (обычно первый поток, который встречает single директиву). Пользователь не должен делать никаких предположений о том, какой поток будет выполнять single раздел. Все остальные потоки пропускают single раздел и останавливаются на барьере в конце single конструкции. Если другие потоки могут продолжаться без ожидания выполнения single раздела, nowait предложение можно указать в директиве single .
#pragma omp parallel
{
#pragma omp single
printf_s("Beginning work1.\n");
work1();
#pragma omp single
printf_s("Finishing work1.\n");
#pragma omp single nowait
printf_s("Finished work1 and beginning work2.\n");
work2();
}
Последовательное упорядочивание A.10
Упорядоченные разделы полезны для последовательного упорядочивания выходных данных из работы, выполняемой параллельно. Следующая программа выводит индексы в последовательном порядке:
#pragma omp for ordered schedule(dynamic)
for (i=lb; i<ub; i+=st)
work(i);
void work(int k)
{
#pragma omp ordered
printf_s(" %d", k);
}
A.11 Фиксированное количество потоков
Некоторые программы используют фиксированное предварительно заданное число потоков для правильного выполнения. Так как параметр по умолчанию для динамической корректировки количества потоков определяется реализацией, такие программы могут отключить возможность динамических потоков и явно задать количество потоков, чтобы обеспечить переносимость. В следующем примере показано, как это сделать с помощью omp_set_dynamic и omp_set_num_threads:
omp_set_dynamic(0);
omp_set_num_threads(16);
#pragma omp parallel shared(x, npoints) private(iam, ipoints)
{
if (omp_get_num_threads() != 16)
abort();
iam = omp_get_thread_num();
ipoints = npoints/16;
do_by_16(x, iam, ipoints);
}
В этом примере программа выполняется правильно, только если она выполняется 16 потоками. Если реализация не поддерживает 16 потоков, то поведение этого примера определяется реализацией.
Число потоков, выполняющих параллельный регион, остается постоянным во время параллельного региона независимо от параметра динамических потоков. Механизм динамических потоков определяет количество потоков, используемых в начале параллельного региона, и сохраняет его константой в течение длительности региона.
A.12 Атомарная директива
В следующем примере можно избежать условий гонки (одновременных обновлений элемента x по многим потокам) с помощью атомарной директивы:
#pragma omp parallel for shared(x, y, index, n)
for (i=0; i<n; i++)
{
#pragma omp atomic
x[index[i]] += work1(i);
y[i] += work2(i);
}
Преимущество использования директивы atomic в этом примере заключается в том, что она позволяет параллельно обновлять два разных элемента x.
Если вместо этого используется критическая директива, все обновления элементов x выполняются последовательно (хотя и не в любом гарантированном порядке).
Директива atomic применяется только к инструкции C или C++ сразу после нее. В результате элементы y не обновляются атомарно в этом примере.
Директива очистки A.13 с списком
В следующем примере используется директива flush для синхронизации определенных объектов между парами потоков:
int sync[NUMBER_OF_THREADS];
float work[NUMBER_OF_THREADS];
#pragma omp parallel private(iam,neighbor) shared(work,sync)
{
iam = omp_get_thread_num();
sync[iam] = 0;
#pragma omp barrier
// Do computation into my portion of work array
work[iam] = ...;
// Announce that I am done with my work
// The first flush ensures that my work is
// made visible before sync.
// The second flush ensures that sync is made visible.
#pragma omp flush(work)
sync[iam] = 1;
#pragma omp flush(sync)
// Wait for neighbor
neighbor = (iam>0 ? iam : omp_get_num_threads()) - 1;
while (sync[neighbor]==0)
{
#pragma omp flush(sync)
}
// Read neighbor's values of work array
... = work[neighbor];
}
A.14 Директива очистки без списка
Следующий пример (для раздела 2.6.5) различает общие объекты, затронутые директивой flush без списка общих объектов, которые не затронуты:
// omp_flush_without_list.c
#include <omp.h>
int x, *p = &x;
void f1(int *q)
{
*q = 1;
#pragma omp flush
// x, p, and *q are flushed
// because they are shared and accessible
// q is not flushed because it is not shared.
}
void f2(int *q)
{
#pragma omp barrier
*q = 2;
#pragma omp barrier
// a barrier implies a flush
// x, p, and *q are flushed
// because they are shared and accessible
// q is not flushed because it is not shared.
}
int g(int n)
{
int i = 1, j, sum = 0;
*p = 1;
#pragma omp parallel reduction(+: sum) num_threads(10)
{
f1(&j);
// i, n and sum were not flushed
// because they were not accessible in f1
// j was flushed because it was accessible
sum += j;
f2(&j);
// i, n, and sum were not flushed
// because they were not accessible in f2
// j was flushed because it was accessible
sum += i + j + *p + n;
}
return sum;
}
int main()
{
}
A.15 Количество используемых потоков
Рассмотрим следующий неправильный пример (для раздела 3.1.2):
np = omp_get_num_threads(); // misplaced
#pragma omp parallel for schedule(static)
for (i=0; i<np; i++)
work(i);
Вызов omp_get_num_threads() возвращает значение 1 в последовательном разделе кода, поэтому np всегда будет равен 1 в предыдущем примере. Чтобы определить количество потоков, которые будут развернуты для параллельного региона, вызов должен находиться в параллельном регионе.
В следующем примере показано, как переписать эту программу без включения запроса на количество потоков:
#pragma omp parallel private(i)
{
i = omp_get_thread_num();
work(i);
}
Блокировки A.16
В следующем примере (для раздела 3.2) аргумент функции блокировки должен иметь тип omp_lock_t, и что его не нужно очищать. Функции блокировки вызывают простой поток во время ожидания записи в первый критически важный раздел, но для выполнения других действий во время ожидания записи во второй. Блоки omp_set_lock функций, но omp_test_lock функция не позволяет выполнять работу skip() .
// omp_using_locks.c
// compile with: /openmp /c
#include <stdio.h>
#include <omp.h>
void work(int);
void skip(int);
int main() {
omp_lock_t lck;
int id;
omp_init_lock(&lck);
#pragma omp parallel shared(lck) private(id)
{
id = omp_get_thread_num();
omp_set_lock(&lck);
printf_s("My thread id is %d.\n", id);
// only one thread at a time can execute this printf
omp_unset_lock(&lck);
while (! omp_test_lock(&lck)) {
skip(id); // we do not yet have the lock,
// so we must do something else
}
work(id); // we now have the lock
// and can do the work
omp_unset_lock(&lck);
}
omp_destroy_lock(&lck);
}
Вложенные блокировки A.17
В следующем примере (в разделе 3.2) показано, как можно использовать вложенную блокировку для синхронизации обновлений как со всей структурой, так и с одним из его членов.
#include <omp.h>
typedef struct {int a,b; omp_nest_lock_t lck;} pair;
void incr_a(pair *p, int a)
{
// Called only from incr_pair, no need to lock.
p->a += a;
}
void incr_b(pair *p, int b)
{
// Called both from incr_pair and elsewhere,
// so need a nestable lock.
omp_set_nest_lock(&p->lck);
p->b += b;
omp_unset_nest_lock(&p->lck);
}
void incr_pair(pair *p, int a, int b)
{
omp_set_nest_lock(&p->lck);
incr_a(p, a);
incr_b(p, b);
omp_unset_nest_lock(&p->lck);
}
void f(pair *p)
{
extern int work1(), work2(), work3();
#pragma omp parallel sections
{
#pragma omp section
incr_pair(p, work1(), work2());
#pragma omp section
incr_b(p, work3());
}
}
A.18 Вложенный для директив
Следующий пример forвложения директив соответствует требованиям, так как внутренние и внешние for директивы привязываются к разным параллельным регионам:
#pragma omp parallel default(shared)
{
#pragma omp for
for (i=0; i<n; i++)
{
#pragma omp parallel shared(i, n)
{
#pragma omp for
for (j=0; j<n; j++)
work(i, j);
}
}
}
Также соответствует следующий вариант предыдущего примера:
#pragma omp parallel default(shared)
{
#pragma omp for
for (i=0; i<n; i++)
work1(i, n);
}
void work1(int i, int n)
{
int j;
#pragma omp parallel default(shared)
{
#pragma omp for
for (j=0; j<n; j++)
work2(i, j);
}
return;
}
Примеры A.19, показывающие неправильное вложение директив общего доступа к работе
Примеры в этом разделе иллюстрируют правила вложения директив.
Следующий пример не соответствует требованиям, так как внутренние и внешние for директивы вложены и привязываются к той же parallel директиве:
void wrong1(int n)
{
#pragma omp parallel default(shared)
{
int i, j;
#pragma omp for
for (i=0; i<n; i++) {
#pragma omp for
for (j=0; j<n; j++)
work(i, j);
}
}
}
Следующая динамически вложенная версия предыдущего примера также не соответствует требованиям:
void wrong2(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++)
work1(i, n);
}
}
void work1(int i, int n)
{
int j;
#pragma omp for
for (j=0; j<n; j++)
work2(i, j);
}
Следующий пример не соответствует требованиям, так как for вложенные директивы и single директивы привязываются к одному и тому же параллельному региону:
void wrong3(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++) {
#pragma omp single
work(i);
}
}
}
Следующий пример не соответствует требованиям, так как barrier директива внутри for может привести к взаимоблокировке:
void wrong4(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++) {
work1(i);
#pragma omp barrier
work2(i);
}
}
}
Следующий пример не соответствует требованиям, так как barrier результаты взаимоблокировки из-за того, что только один поток за раз может ввести критически важный раздел:
void wrong5()
{
#pragma omp parallel
{
#pragma omp critical
{
work1();
#pragma omp barrier
work2();
}
}
}
Следующий пример не соответствует требованиям, так как barrier результаты взаимоблокировки возникают из-за того, что только один поток выполняет single раздел:
void wrong6()
{
#pragma omp parallel
{
setup();
#pragma omp single
{
work1();
#pragma omp barrier
work2();
}
finish();
}
}
Директивы барьера привязки A.20
Правила привязки директив вызывают директиву barrier для привязки к ближайшей директиве parallel . Дополнительные сведения о привязке директив см . в разделе 2.8.
В следующем примере вызов из main к sub2 соответствует требованиям, так как (в под3) привязывается к параллельному региону в barrier. Вызов от main к sub1 соответствует требованиям, так как barrier привязка к параллельному региону в подзадаче под2. Вызов от main к sub3 соответствует требованиям, так как он не привязывается к какой-либо параллельной области и игнорируется.barrier Кроме того, barrier только синхронизирует команду потоков в заключающей параллельной области, а не все потоки, созданные в под1.
int main()
{
sub1(2);
sub2(2);
sub3(2);
}
void sub1(int n)
{
int i;
#pragma omp parallel private(i) shared(n)
{
#pragma omp for
for (i=0; i<n; i++)
sub2(i);
}
}
void sub2(int k)
{
#pragma omp parallel shared(k)
sub3(k);
}
void sub3(int n)
{
work(n);
#pragma omp barrier
work(n);
}
Переменные области A.21 с частным предложением
Значения i и j в следующем примере не определены при выходе из параллельного региона:
int i, j;
i = 1;
j = 2;
#pragma omp parallel private(i) firstprivate(j)
{
i = 3;
j = j + 2;
}
printf_s("%d %d\n", i, j);
Дополнительные сведения о предложении private см . в разделе 2.7.2.1.
A.22 Предложение default(none)
В следующем примере переменные, затронутые default(none) предложением, отличаются от переменных, которые не являются:
// openmp_using_clausedefault.c
// compile with: /openmp
#include <stdio.h>
#include <omp.h>
int x, y, z[1000];
#pragma omp threadprivate(x)
void fun(int a) {
const int c = 1;
int i = 0;
#pragma omp parallel default(none) private(a) shared(z)
{
int j = omp_get_num_thread();
//O.K. - j is declared within parallel region
a = z[j]; // O.K. - a is listed in private clause
// - z is listed in shared clause
x = c; // O.K. - x is threadprivate
// - c has const-qualified type
z[i] = y; // C3052 error - cannot reference i or y here
#pragma omp for firstprivate(y)
for (i=0; i<10 ; i++) {
z[i] = y; // O.K. - i is the loop control variable
// - y is listed in firstprivate clause
}
z[i] = y; // Error - cannot reference i or y here
}
}
Дополнительные сведения о предложении default см . в разделе 2.7.2.5.
Примеры упорядоченной директивы A.23
Можно иметь множество упорядоченных разделов с for указанным предложением ordered . Первый пример не соответствует требованиям, так как API указывает следующее правило:
"Итерация цикла с for конструкцией не должна выполнять одну и ту же ordered директиву более одного раза, и она не должна выполнять несколько ordered директив". (См . раздел 2.6.6.6.)
В этом несоответствующее примере все итерации выполняют два упорядоченных раздела:
#pragma omp for ordered
for (i=0; i<n; i++)
{
...
#pragma omp ordered
{ ... }
...
#pragma omp ordered
{ ... }
...
}
В следующем примере показано for несколько упорядоченных разделов:
#pragma omp for ordered
for (i=0; i<n; i++)
{
...
if (i <= 10)
{
...
#pragma omp ordered
{ ... }
}
...
(i > 10)
{
...
#pragma omp ordered
{ ... }
}
...
}
Пример закрытого предложения A.24
Частное предложение параллельного региона действует только для лексической степени региона, а не для динамической степени региона. Таким образом, в следующем примере любая переменная в цикле в подпрограмме for относится к частной копии a, а использование в подпрограмме g относится к глобальной.
int a;
void f(int n)
{
a = 0;
#pragma omp parallel for private(a)
for (int i=1; i<n; i++)
{
a = i;
g(i, n);
d(a); // Private copy of "a"
...
}
...
void g(int k, int n)
{
h(k,a); // The global "a", not the private "a" in f
}
A.25 Примеры предложения атрибута данных copyprivate
Пример 1.Предложение copyprivate можно использовать для трансляции значений, полученных одним потоком непосредственно ко всем экземплярам частных переменных в других потоках.
float x, y;
#pragma omp threadprivate(x, y)
void init( )
{
float a;
float b;
#pragma omp single copyprivate(a,b,x,y)
{
get_values(a,b,x,y);
}
use_values(a, b, x, y);
}
Если подпрограмма инициализации вызывается из последовательного региона, его поведение не влияет на наличие директив. После вызова подпрограммы get_values выполняется одним потоком, ни один поток не покидает конструкцию, пока частные объекты, назначенные a, b, x и y во всех потоках, не будут определены со значениями, считываемыми.
Пример 2. В отличие от предыдущего примера, предположим, что чтение должно выполняться определенным потоком, например главным потоком. В этом случае copyprivate предложение нельзя использовать для прямой трансляции, но его можно использовать для предоставления доступа к временному общему объекту.
float read_next( )
{
float * tmp;
float return_val;
#pragma omp single copyprivate(tmp)
{
tmp = (float *) malloc(sizeof(float));
}
#pragma omp master
{
get_float( tmp );
}
#pragma omp barrier
return_val = *tmp;
#pragma omp barrier
#pragma omp single
{
free(tmp);
}
return return_val;
}
Пример 3. Предположим, что количество объектов блокировки, необходимых в параллельном регионе, невозможно легко определить перед вводом. Предложение copyprivate можно использовать для предоставления доступа к объектам общей блокировки, выделенным в этом параллельном регионе.
#include <omp.h>
omp_lock_t *new_lock()
{
omp_lock_t *lock_ptr;
#pragma omp single copyprivate(lock_ptr)
{
lock_ptr = (omp_lock_t *) malloc(sizeof(omp_lock_t));
omp_init_lock( lock_ptr );
}
return lock_ptr;
}
A.26 Директива threadprivate
В следующих примерах показано, как использовать директиву threadprivate для предоставления каждому потоку отдельного счетчика.
Пример 1
int counter = 0;
#pragma omp threadprivate(counter)
int sub()
{
counter++;
return(counter);
}
Пример 2
int sub()
{
static int counter = 0;
#pragma omp threadprivate(counter)
counter++;
return(counter);
}
Массивы переменной длины A.27 C99
В следующем примере показано, как использовать массивы переменной длины C99 (VLAs) в директиве firstprivate .
Примечание.
Массивы переменной длины в настоящее время не поддерживаются в Visual C++.
void f(int m, int C[m][m])
{
double v1[m];
...
#pragma omp parallel firstprivate(C, v1)
...
}
A.28 Предложение num_threads
В следующем примере показано предложение num_threads . Параллельный регион выполняется не более 10 потоков.
#include <omp.h>
int main()
{
omp_set_dynamic(1);
...
#pragma omp parallel num_threads(10)
{
... parallel region ...
}
}
Конструкции общего доступа к работе В.29 внутри критической конструкции
В следующем примере показано использование конструкции общего доступа к работе внутри critical конструкции. Этот пример соответствует требованиям, так как конструкция общего доступа к работе и critical конструкция не привязываются к одному и тому же параллельному региону.
void f()
{
int i = 1;
#pragma omp parallel sections
{
#pragma omp section
{
#pragma omp critical (name)
{
#pragma omp parallel
{
#pragma omp single
{
i++;
}
}
}
}
}
}
Реприватизация A.30
В следующем примере показано повторное получение переменных. Частные переменные можно пометить private снова в вложенной директиве. Эти переменные не нужно совместно использовать в заключающей параллельной области.
int i, a;
...
#pragma omp parallel private(a)
{
...
#pragma omp parallel for private(a)
for (i=0; i<10; i++)
{
...
}
}
Функции блокировки, безопасные для потоков A.31
В следующем примере C++ показано, как инициализировать массив блокировок в параллельном регионе с помощью omp_init_lock.
// A_13_omp_init_lock.cpp
// compile with: /openmp
#include <omp.h>
omp_lock_t *new_locks() {
int i;
omp_lock_t *lock = new omp_lock_t[1000];
#pragma omp parallel for private(i)
for (i = 0 ; i < 1000 ; i++)
omp_init_lock(&lock[i]);
return lock;
}
int main () {}