Главная страница

С. антоновпараллельноепрограммированиесиспользованиемтехнологииopenMP


Скачать 0.55 Mb.
НазваниеС. антоновпараллельноепрограммированиесиспользованиемтехнологииopenMP
Дата03.04.2022
Размер0.55 Mb.
Формат файлаpdf
Имя файлаOpenMP.pdf
ТипУчебное пособие
#438994
страница7 из 8
1   2   3   4   5   6   7   8
critical
. Переменная
n
объявлена вне параллельной области, поэтому по умолчанию является об- щей. Критическая секция позволяет разграничить доступ к переменной
n
Каждая нить по очереди присвоит
n
свой номер и затем напечатает получен- ное значение.
#include
#include
int main(int argc, char *argv[])
{
int n;
#pragma omp parallel
{
#pragma omp critical
{
n=omp_get_thread_num();
printf("
Нить
%d\n", n);
}
}
}
Пример 26a. Директива critical наязыкеСи.

59
program example26b
include "omp_lib.h"
integer n
!$omp parallel
!$omp critical
n=omp_get_thread_num()
print *, "
Нить
", n
!$omp end critical
!$omp end parallel
end
Пример 26b. Директива critical наязыкеФортран.
Если бы в примере 26 не была указана директива
critical
, результат вы- полнения программы был бы непредсказуем. С директивой
critical
поря- док вывода результатов может быть произвольным, но это всегда будет на- бор одних и тех же чисел от
0
до
OMP_NUM_THREADS-1
. Конечно, подобного же результата можно было бы добиться другими способами, например, объя- вив переменную
n
локальной, тогда каждая нить работала бы со своей копией этой переменной. Однако в исполнении этих фрагментов разница существен- ная.
Если есть критическая секция, то в каждый момент времени фрагмент будет обрабатываться лишь какой-либо одной нитью. Остальные нити, даже если они уже подошли к данной точке программы и готовы к работе, будут ожи- дать своей очереди. Если критической секции нет, то все нити могут одно- временно выполнить данный участок кода. С одной стороны, критические секции предоставляют удобный механизм для работы с общими переменны- ми. Но с другой стороны, пользоваться им нужно осмотрительно, поскольку критические секции добавляют последовательные участки кода в параллель- ную программу, что может снизить её эффективность.
Директива
atomic
Частым случаем использования критических секций на практике является обновление общих переменных. Например, если переменная
sum
является общей и оператор вида
sum=sum+expr
находится в параллельной области программы, то при одновременном выполнении данного оператора несколь- кими нитями можно получить некорректный результат. Чтобы избежать та- кой ситуации можно воспользоваться механизмом критических секций или специально предусмотренной для таких случаев директивой
atomic
Си:
#pragma omp atomic

60
Фортран:
!$omp atomic
Данная директива относится к идущему непосредственно за ней оператору присваивания (на используемые в котором конструкции накладываются дос- таточно понятные ограничения), гарантируя корректную работу с общей пе- ременной, стоящей в его левой части. На время выполнения оператора бло- кируется доступ к данной переменной всем запущенным в данный момент нитям, кроме нити, выполняющей операцию. Атомарной является только ра- бота с переменной в левой части оператора присваивания, при этом вычисле- ния в правой части не обязаны быть атомарными.
Пример 27 иллюстрирует применение директивы
atomic
. В данном примере производится подсчет общего количества порожденных нитей. Для этого ка- ждая нить увеличивает на единицу значение переменной
count
. Для того, чтобы предотвратить одновременное изменение несколькими нитями значе- ния переменной, стоящей в левой части оператора присваивания, использу- ется директива
atomic
#include
#include
int main(int argc, char *argv[])
{
int count = 0;
#pragma omp parallel
{
#pragma omp atomic
count++;
}
printf("
Число нитей
: %d\n", count);
}
Пример 27a. Директива atomic наязыкеСи.
program example27b
include "omp_lib.h"
integer count
count = 0
!$omp parallel
!$omp atomic
count=count+1
!$omp end parallel
print *, "
Число нитей
: ", count
end
Пример 27b. Директива atomic наязыкеФортран.

61
Замки'(
Один из вариантов синхронизации в OpenMP реализуется через механизм замков (locks). В качестве замков используются общие целочисленные пере- менные (размер должен быть достаточным для хранения адреса). Данные пе- ременные должны использоваться только как параметры примитивов син- хронизации.
Замок может находиться в одном из трёх состояний: неинициализированный, разблокированный или заблокированный. Разблокированный замок может быть захвачен некоторой нитью. При этом он переходит в заблокированное состояние. Нить, захватившая замок, и только она может его освободить, по- сле чего замок возвращается в разблокированное состояние.
Есть два типа замков: простыезамки и множественныезамки. Множест- венный замок может многократно захватываться одной нитью перед его ос- вобождением, в то время как простой замок может быть захвачен только од- нажды. Для множественного замка вводится понятие коэффициента захваченности (nesting count). Изначально он устанавливается в ноль, при каждом следующем захватывании увеличивается на единицу, а при каждом освобождении уменьшается на единицу. Множественный замок считается разблокированным, если его коэффициент захваченности равен нулю.
Для инициализации простого или множественного замка используются соот- ветственно функции
omp_init_lock()
и
omp_init_nest_lock()
Си:
void omp_init_lock(omp_lock_t *lock);
void omp_init_nest_lock(omp_nest_lock_t *lock);
Фортран:
subroutine omp_init_lock(svar)
integer (kind=omp_lock_kind) svar
subroutine omp_init_nest_lock(nvar)
integer (kind=omp_nest_lock_kind) nvar
После выполнения функции замок переводится в разблокированное состоя- ние. Для множественного замка коэффициент захваченности устанавливается в ноль.
Функции
omp_destroy_lock()
и
omp_destroy_nest_lock()
используются для переведения простого или множественного замка в неинициализирован- ное состояние.

62
Си:
void omp_destroy_lock(omp_lock_t *lock);
void omp_destroy_nest_lock(omp_nest_lock_t *lock);
Фортран:
subroutine omp_destroy_lock(svar)
integer (kind=omp_lock_kind) svar
subroutine omp_destroy_nest_lock(nvar)
integer (kind=omp_nest_lock_kind) nvar
Для захватывания замка используются функции
omp_set_lock()
и
omp_set_nest_lock()
Си:
void omp_set_lock(omp_lock_t *lock);
void omp_set_nest_lock(omp_nest_lock_t *lock);
Фортран:
subroutine omp_set_lock(svar)
integer (kind=omp_lock_kind) svar
subroutine omp_set_nest_lock(nvar)
integer (kind=omp_nest_lock_kind) nvar
Вызвавшая эту функцию нить дожидается освобождения замка, а затем за- хватывает его. Замок при этом переводится в заблокированное состояние.
Если множественный замок уже захвачен данной нитью, то нить не блокиру- ется, а коэффициент захваченности увеличивается на единицу.
Для освобождения замка используются функции
omp_unset_lock()
и
omp_unset_nest_lock()
Си:
void omp_unset_lock(omp_lock_t *lock);
void omp_unset_nest_lock(omp_lock_t *lock);
Фортран:
subroutine omp_unset_lock(svar)
integer (kind=omp_lock_kind) svar
subroutine omp_unset_nest_lock(nvar)
integer (kind=omp_nest_lock_kind) nvar
Вызов этой функции освобождает простой замок, если он был захвачен вы- звавшей нитью. Для множественного замка уменьшает на единицу коэффи- циент захваченности. Если коэффициент станет равен нулю, замок освобож- дается. Если после освобождения замка есть нити, заблокированные на операции, захватывающей данный замок, замок будет сразу же захвачен од- ной из ожидающих нитей.

63
Пример 28 иллюстрирует применение технологии замков. Переменная
lock
используется для блокировки. В последовательной области производится инициализация данной переменной с помощью функции
omp_init_lock()
В начале параллельной области каждая нить присваивает переменной
n
свой порядковый номер. После этого с помощью функции
omp_set_lock()
одна из нитей выставляет блокировку, а остальные нити ждут, пока нить, вызвав- шая эту функцию, не снимет блокировку с помощью функции
omp_unset_lock()
. Все нити по очереди выведут сообщения
"
Начало за
-
крытой секции
..."
и
"
Конец закрытой секции
..."
, при этом между двумя сообщениями от одной нити не могут встретиться сообщения от другой нити.
В конце с помощью функции
omp_destroy_lock()
происходит освобожде- ние переменной
lock
#include
#include
omp_lock_t lock;
int main(int argc, char *argv[])
{
int n;
omp_init_lock(&lock);
#pragma omp parallel private (n)
{
n=omp_get_thread_num();
omp_set_lock(&lock);
printf("
Начало закрытой секции
,
нить
%d\n", n);
sleep(5);
printf("
Конец закрытой секции
,
нить
%d\n", n);
omp_unset_lock(&lock);
}
omp_destroy_lock(&lock);
}
Пример 28a. ИспользованиезамковнаязыкеСи.
program example27b
include "omp_lib.h"
integer (kind=omp_lock_kind) lock
integer n
call omp_init_lock(lock)
!$omp parallel private (n)
n=omp_get_thread_num()
call omp_set_lock(lock)
print *, "
Начало закрытой секции
,
нить
", n
call sleep(5)
print *, "
Конец закрытой секции
,
нить
", n
call omp_unset_lock(lock)
!$omp end parallel
call omp_destroy_lock(lock)
end
Пример 28b. ИспользованиезамковнаязыкеФортран.

64
Для неблокирующей попытки захвата замка используются функции
omp_test_lock()
и
omp_test_nest_lock()
Си:
int omp_test_lock(omp_lock_t *lock);
int omp_test_nest_lock(omp_lock_t *lock);
Фортран:
logical function omp_test_lock(svar)
integer (kind=omp_lock_kind) svar
integer function omp_test_nest_lock(nvar)
integer (kind=omp_lock_kind) nvar
Данная функция пробует захватить указанный замок. Если это удалось, то для простого замка функция возвращает
1
(для Фортрана –
.TRUE.
), а для множественного замка – новый коэффициент захваченности. Если замок за- хватить не удалось, в обоих случаях возвращается
0
(для простого замка на языке Фортран –
.FALSE.
).
Пример 29 иллюстрирует применение технологии замков и использование функции
omp_test_lock()
. В данном примере переменная
lock
использует- ся для блокировки. В начале производится инициализация данной перемен- ной с помощью функции
omp_init_lock()
. В параллельной области каждая нить присваивает переменной
n
свой порядковый номер. После этого с по- мощью функции
omp_test_lock()
нити попытаются выставить блокировку.
Одна из нитей успешно выставит блокировку, другие же нити напечатают сообщение
"
Секция закрыта
..."
, приостановят работу на две секунды с помощью функции
sleep()
, а после снова будут пытаться установить блоки- ровку. Нить, которая установила блокировку, должна снять её с помощью функции
omp_unset_lock()
. Таким образом, код, находящийся между функ- циями установки и снятия блокировки, будет выполнен каждой нитью по очереди. В данном случае, все нити по очереди выведут сообщения
"
Начало закрытой секции
..."
и
"
Конец закрытой секции
..."
, но при этом между двумя сообщениями от одной нити могут встретиться сообщения от других нитей о неудачной попытке войти в закрытую секцию. В конце с помощью функции
omp_destroy_lock()
происходит освобождение переменной
lock

65
#include
#include
int main(int argc, char *argv[])
{
omp_lock_t lock;
int n;
omp_init_lock(&lock);
#pragma omp parallel private (n)
{
n=omp_get_thread_num();
while (!omp_test_lock (&lock))
{
printf("
Секция закрыта
,
нить
%d\n", n);
sleep(2);
}
printf("
Начало закрытой секции
,
нить
%d\n", n);
sleep(5);
printf("
Конец закрытой секции
,
нить
%d\n", n);
omp_unset_lock(&lock);
}
omp_destroy_lock(&lock);
}
Пример 29a. Функция omp_test_lock() наязыкеСи.
program example29b
include "omp_lib.h"
integer (kind=omp_lock_kind) lock
integer n
call omp_init_lock(lock)
!$omp parallel private (n)
n=omp_get_thread_num()
do while (.not. omp_test_lock(lock))
print *, "
Секция закрыта
,
нить
", n
call sleep(2)
end do
print *, "
Начало закрытой секции
,
нить
", n
call sleep(5)
print *, "
Конец закрытой секции
,
нить
", n
call omp_unset_lock(lock)
!$omp end parallel
call omp_destroy_lock(lock)
end
Пример 29b. Функция omp_test_lock() наязыкеФортран.
Использование замков является наиболее гибким механизмом синхрониза- ции, поскольку с помощью замков можно реализовать все остальные вариан- ты синхронизации.

66
Директива
flush
Поскольку в современных параллельных вычислительных системах может использоваться сложная структура и иерархия памяти, пользователь должен иметь гарантии того, что в необходимые ему моменты времени все нити бу- дут видеть единый согласованный образ памяти. Именно для этих целей и предназначена директива
flush
Си:
#pragma omp flush [(
список
)]
Фортран:
!$omp flush [(
список
)]
Выполнение данной директивы предполагает, что значения всех переменных
(или переменных из списка, если он задан), временно хранящиеся в регист- рах и кэш-памяти текущей нити, будут занесены в основную память; все из- менения переменных, сделанные нитью во время работы, станут видимы ос- тальным нитям; если какая-то информация хранится в буферах вывода, то буферы будут сброшены и т.п. При этом операция производится только с данными вызвавшей нити, данные, изменявшиеся другими нитями, не затра- гиваются. Поскольку выполнение данной директивы в полном объёме может повлечь значительных накладных расходов, а в данный момент нужна гаран- тия согласованного представления не всех, а лишь отдельных переменных, то эти переменные можно явно перечислить в директиве списком. До полного завершения операции никакие действия с перечисленными в ней переменны- ми не могут начаться.
Неявно
flush
без параметров присутствует в директиве
barrier
, на входе и выходе областей действия директив
parallel
,
critical
,
ordered
, на выхо- де областей распределения работ, если не используется опция
nowait
, в вы- зовах функций
omp_set_lock()
,
omp_unset_lock()
,
omp_test_lock()
,
omp_set_nest_lock()
,
omp_unset_nest_lock()
,
omp_test_nest_lock()
, если при этом замок устанавливается или снимается, а также перед порожде- нием и после завершения любой задачи (
task
). Кроме того,
flush
вызывает- ся для переменной, участвующей в операции, ассоциированной с директивой
atomic
. Заметим, что
flush
не применяется на входе области распределения работ, а также на входе и выходе области действия директивы
master
Задания

Что произойдёт, если барьер встретится не во всех нитях, исполняю- щих текущую параллельную область?

Могут ли две нити одновременно находиться в различных критических секциях?

67

В чём заключается разница в использовании критических секций и ди- рективы
atomic
?

Смоделируйте при помощи механизма замков: o
барьерную синхронизацию; o
критическую секцию.

Придумайте пример на использование множественного замка.

Когда возникает необходимость в использовании директивы
flush
?

Реализуйте параллельный алгоритм метода Гаусса решения систем ли- нейных алгебраических уравнений. Выберите оптимальные варианты распараллеливания и проведите анализ эффективности реализации.

68
Дополнительные переменные среды и
функции
Переменная
OMP_MAX_ACTIVE_LEVELS
задаёт максимально допустимое коли- чество вложенных параллельных областей. Значение может быть установле- но при помощи вызова функции
1   2   3   4   5   6   7   8


написать администратору сайта