Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Ниже приведены примеры конструкций, определенных в этом документе. Утверждение, следующее за директивой, является составным только в случае необходимости, а не составное утверждение отделяется от предшествующей ему директивы отступом.
A.1 Простой цикл в параллельном режиме
В следующем примере показано, как параллелизировать цикл с помощью директивы parallel for. Переменная итерации цикла является частной по умолчанию, поэтому не нужно явно указывать ее в частном предложении.
#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 Параллельные
В следующем примере (для раздела 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 соответствует требованиям, так как barrier (в sub3) привязывается к параллельному региону в sub2. Вызов от main к sub1 соответствует требованиям, так как barrier привязка к параллельному региону в подпрограмме sub2. Вызов из 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 с директивой private
Значения 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
Частная клаузула параллельной области действует только в пределах лексической области, а не для динамической области. Таким образом, в следующем примере любое использование переменной a в цикле функции f относится к частной копии a, а использование в функции g относится к глобальной a.
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);
}
Если подпрограмма init вызывается из последовательного региона, её поведение не зависит от наличия директив. После того как подпрограмма 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 ...
}
}
Конструкции разделения работы A.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 () {}