
2. Оператор sections
Оператор sections определяет набор программных конструкций не итерационного типа (т.е. не цикл), которые делятся между нитями в группе. Каждая секция выполняется каждой нитью один раз. Синтаксис директивы sections:
#pragma omp sections [оператор [оператор] ...]
{
[#pragma omp section]
структурированный блок
[#pragma omp section]
структурированный блок
…
}
Оператор может иметь один из следующих типов:
private(список)
firstprivate(список)
lastprivate(список)
reduction(операция: список)
nowait
Каждой секции операторов предшествует оператор sections. Каждый внутренний блок операторов должен содержать оператор section. Если оператор nowait не указан, то конструкция sections завершится барьерной синхронизацией.
Ограничения к применению директивы sections:
1. Оператор section не должен появляться вне оператора sections.
2. Только один оператор nowait может появиться в директиве sections.
Пример: каждый блок section выполняется индивидуальной нитью. Если количество нитей равно 3, то выделенные в директиве sections три блока инструкций, определенные директивой section будут выполнены параллельно.
int s=0, p=1;
#pragma omp parallel sections
{
#pragma omp section // блок 1, вычисляющий сумму элементов массива a
for (i=0; i<10; i++) {
s = s + b[i];
}
#pragma omp section // блок 2, вычисляющий сумму элементов массива b
for (i=0; i<10; i++) {
p = p * b[i];
}
#pragma omp section //блок 3, вычисляющий значения элементов массива c
for (i=0; i<10; i++) { // как сумму числовых значений элементов массивов a и b
c[i] = a[i] + b[i];
}
}
Пример: каждый блок выводит на экран номер нити, на котором он выполняется. В первом блоке отсутствует директива section, такой синтаксис допускается стандартом OpenMP, но только в первом блоке.
#pragma omp parallel sections
{ // блок инструкций 1, директива section отсутствует
printf ("Hello from thread %d\n", omp_get_thread_num());
#pragma omp section // блок инструкций 2
printf ("Hello from thread %d\n", omp_get_thread_num());
#pragma omp section // блок инструкций 3
printf ("Hello from thread %d\n", omp_get_thread_num());
}
3. Оператор single
Оператор single определяет структурированный блок операторов, который выполняется только одной нитью в группе (не обязательно основной нитью). Такой участок кода будет выполнен нитью, первой дошедшей в данную точку программы.
Синтаксис директивы single:
#pragma omp single [оператор [оператор] ...]
структурированный блок
Оператор может иметь один из следующих типов:
private(список)
firstprivate(список)
nowait
Если nowait не указан, то single завершится неявной барьерной синхронизацией. В операторе single может использоваться только один оператор nowait.
Пример: каждый блок инструкций выполняется только одной нитью. Количество нитей, используемых для выполнения программы равно 3. В начале конструкции каждая нить выводит сообщение о создании и свой номер.
#pragma omp parallel
{
printf("Create thread %d\n", omp_get_thread_num());
#pragma omp single
{ printf("Beginning work1.Thread %d\n", omp_get_thread_num());
work1();
}
#pragma omp single
printf("Finishing work1. Thread %d\n", omp_get_thread_num()); // барьерная
//синхронизация, в конструкции отсутствует nowait т.е.
// блок ожидает окончания работы предыдущего блока
#pragma omp single nowait
{ printf("Beginning work2. Thread %d\n", omp_get_thread_num()); // нет барьерной
//синхронизации, т.к. в конструкции используется nowait
work2();
}
}
Лекция 10
Технология программирования OpenMP. Операции синхронизации потоков. Переменные окружения и стандартные функции интерфейса OpenMP.
Операции синхронизации:
master
critical
atomic
barrier
flush
ordered
1. master определяет блок программы, который будет выполнен только основной нитью группы. Остальные нити пропускают данный участок и продолжают работу с выполнения оператора, расположенного следом за директивой master. Синтаксис оператора master:
#pragma omp master
{
Блок программы
}
Барьерная синхронизация при входе или выходе из этой секции выполняться не будет.
2. critical определяет блок программы, который ограничивает выполнение структурированного блока только одной нитью в одно и тоже время. Синтаксис оператора critical:
#pragma omp critical [(имя_критической_секции)]
{
структурированный блок программы
}
Одномоментно, критическую секцию может выполнять только одна нить из всех нитей с данным именем. Если критическая секция уже выполняется какой-либо нитью P0, то все другие нити, выполнившие директиву для секции с данным именем, будут заблокированы, пока нить P0 не закончит выполнение данной критической секции, после чего одна из заблокированных на входе нитей войдет в секцию. Если на входе в критическую секцию стояло несколько нитей, то одна из них случайным образом выбирается, остальные заблокированные нити продолжают ожидание. Критические секции, не имеющие имен ассоциируются с одним и тем же именем критической секции.
Частым случаем использования критических секций на практике является обновление общих переменных. Например, если переменная SUM является общей и оператор вида SUM=SUM+Expr находится в параллельной секции программы, то при одновременном выполнении данного оператора несколькими нитями можно получить некорректный результат. Чтобы избежать такой ситуации можно воспользоваться механизмом критических секций или специально предусмотренным для таких случаев оператором atomic.
Пример: иллюстрирует моделирование очереди, в которой задание выбирается из очереди и обрабатывается. Для защиты от многократной выборки нитями из очереди одного и того же задания операция выборки должна выполняться в критической секции. Т.к. две очереди в этом примере идентичны, то они защищены критическими секциями с разными именами: 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. В программе выполнено итерационное распараллеливание с использованием критической секции внутри каждой порции итераций.
Массив a разбивается на части(цикл по i) для поиска максимального значения в каждой порции элементов массива, критическая секция выполняет обновление значения общей переменной max по следующему принципу: если какая-то нить находит новое максимальное значение, начинает выполняться блок критической секции, в этом случае все нити останавливают свою работу и по очереди входят в критическую секцию, выполняя обновление значение max.
После того, как все нити по очереди прошли критическую секцию и обновили значение max, поиск максимального значения продолжается в параллельном режиме в каждой порции каждой нити.
В данном примере критическая секция не имеет имени.
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>