С. антоновпараллельноепрограммированиесиспользованиемтехнологииopenMP
Скачать 0.55 Mb.
|
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 ? • Реализуйте параллельный алгоритм метода Гаусса решения систем ли- нейных алгебраических уравнений. Выберите оптимальные варианты распараллеливания и проведите анализ эффективности реализации. |