Опубликован: 15.04.2008 | Уровень: специалист | Доступ: платный
Лекция 5:

Отладка программ в OpenMP

< Лекция 4 || Лекция 5: 123 || Лекция 6 >
Аннотация: В этой лекции рассматривается ряд проблем, возникающих при отладке параллельных программ в OpenMP. Первый раздел настоящей лекции посвящен рассмотрению условий состязательности. Во втором разделе изложена проблема мертвой блокировки. И, наконец, в третьем разделе приведены средства автоматизированной отладки в OpenMP.

При написании и отладке программ в OpenMP необходимо обеспечить выполнение целого ряда специфических требований, без которых невозможно гарантировать корректность работы программы. Далее рассмотрим эти требования подробнее.

Во-первых, необходимо обеспечить, чтобы все используемые в программах библиотеки были безопасны с точки зрения потокового выполнения. При этом следует иметь в виду, что все стандартные библиотеки всегда удовлетворяют этому требованию. Таким образом, важно обеспечить выполнение этого требования только по отношению к тем библиотекам, которые создает сам разработчик или его коллеги.

Разрабатывая и отлаживая программы в OpenMP, необходимо помнить, что операции ввода/вывода в параллельных потоках завершаются в непредсказуемом порядке. Поэтому большое внимание следует уделять проблеме синхронизации параллельных процессов, помня при этом, что синхронизация - очень дорогая операция. Поэтому ею следует пользоваться как можно реже и только в случае крайней необходимости.

При разработке программ с использованием OpenMP следует внимательно прослеживать пересечение локальных переменных типа private в параллельных потоках с глобальными переменными. Кроме того, необходимо тщательно отслеживать своевременное обновление значений переменных в общей памяти. Для этого по мере необходимости следует пользоваться директивой OpenMP flush.

Проектируя программы, следует помнить, что удалить неявно установленные барьеры ( barrier ) в операторах цикла можно с помощью предложения OpenMP nowait.

Условия состязательности

При работе программ с общей памятью возникает ряд специфических ошибок, одна из которых связана с так называемыми условиями состязательности ( race conditions ). Ошибки, связанные с условиями состязательности, состоят в непредсказуемом времени завершения параллельных потоков, из-за чего возможны непредсказуемые результаты вычислений. Автоматическое распараллеливание в таких ситуациях затруднено, поскольку компилятору либо трудно, либо вообще невозможно распознать взаимозависимости по данным, возникающие в процессе параллельных вычислений при непредсказуемом времени завершения работы параллельных потоков. В таких ситуациях важно правильно определить последовательность выполнения параллельных потоков, чтобы избежать непредсказуемых результатов.

В качестве примера возникновения условия состязательности рассмотрим фрагмент программы, показанный в примере 5.1.

c$omp parallel sections
      A = B + C
c$omp section
      B = A + C
c$omp section
      C = B + A
c$omp end parallel sections
5.1. Фрагмент параллельной программы с возникновением условия состязательности

Результат вычислений в этом примере будет зависеть от того, в какой последовательности выполняются параллельные потоки. При этом никакой диагностики компилятора о возникновении условия состязательности в программе не выдается. Это существенно усложняет отладку параллельных приложений, если программист заранее не позаботится об исключении таких ситуаций. Устранить некорректность в вычислениях в рассматриваемом примере можно, например, с помощью синхронизации параллельных потоков или с помощью директив загрузки параллельных процессов в OpenMP. Ниже в примере 5.2 приведен пример одной из возможных модификаций фрагмента программы (пример 5.1), в котором устранены недостатки, связанные с наличием условия состязательности. В этой модификации для предотвращения условия состязательности введен счетчик событий ICOUNT и использована директива OpenMP FLUSH для организации корректного обращения к данным. Счетчик событий ICOUNT устанавливает последовательность вычислений: сначала вычисляется A=B+C, потом B=A+C и лишь затем C=B+A. Использование сочетания операторов IF и GOTO в сочетании с директивой OpenMP FLUSH позволило в этом примере провести синхронизацию трех параллельных процессов.

Еще один пример возникновения условия состязательности в программе представлен во фрагменте в примере 5.3. В этом примере условие состязательности возникает из-за неправильного в данном случае использования предложения OpenMP NOWAIT. Для корректной работы программы достаточно просто исключить предложение NOWAIT.

ICOUNT = 0
c$omp parallel sections
      А = В + С
      ICOUNT = 1
c$omp flush ICOUNT
c$omp section
 1000 CONTINUE
c$omp flush ICOUNT
      IF (ICOUNT .LT. 1)GOTO 1000
      B = A + C
      ICOUNT = 2
c$omp flush ICOUNT
c$omp section
 2000 CONTINUE
c$omp flush ICOUNT
      IF (ICOUNT .LT. 2)GOTO 2000
      C = B + A
c$omp end parallel sections
5.2. Пример фрагмента параллельной программы с устранением условия состязательности
c$omp parallel shared (X)
c$omp& private (TMP)
      ID=omp_get_thread_num ()
c$omp do reduction (+ : X)
      DO 100 I = 1, 100
      TMP = WORK(I)
      X = X + TMP
 100  CONTINUE
c$omp end do nowait
      Y( ID ) = X
c$omp end parallel
5.3. Пример возникновения условия состязательности в программе из-за неправильного использования директивы NOWAIT

В примере 5.4 условия состязательности можно избежать, описав переменную TMP в параллельной области как private.

REAL TMP, X
c$omp parallel do reduction (+ : X)
      DO 100 I = 1, 100
      TMP = WORK(I)
      X = X + TMP
  100 CONTINUE
c$omp end do
      Y(ID) = X
c$omp end parallel
5.4. Пример возникновения условия состязательности в программе из-за отсутствия описания переменной TMP как private

Мертвая блокировка

В процессе выполнения программ с общей памятью возможна ситуация, когда один из параллельных потоков ожидает освобождения доступа к объекту, который никогда не будет открыт. Такая ситуация, возникающая в параллельной программе, называется мертвой блокировкой ( deadlock ).

В качестве иллюстрации мертвой блокировки рассмотрим фрагмент программы на языке Fortran, приведенный в примере 5.5.

call omp_init_lock (lcka)
      call omp_init_lock (lckb)
c$omp parallel sections
      call omp_set_lock (lcka)
      call omp_set_lock (lckb)
      call useAandB (res)
      call omp_unset_lock (lckb)
      call omp_unset_lock (lcka)
c$omp section
      call omp_set_lock (lckb)
      call omp_set_lock (lcka)
      call useBandA (res)
      call omp_unset_lock (lcka)
      call omp_unset_lock (lckb)
c$omp end parallel sections
5.5. Пример возникновения мертвой блокировки в параллельной программе

В этом примере при выполнении параллельных потоков возможна ситуация, когда объект А заблокирован в одном параллельном потоке, а объект В - в другом. В результате возникает мертвая блокировка и выполнение подпрограмм useAandB и useBandA не происходит. Однако это не единственная неприятная ситуация, возникающая в этой программе.

Возможно, что при выполнении параллельных потоков в одном потоке окажутся заблокированными оба объекта A и B, тогда при выполнении рассматриваемой программы возникает условие состязательности, рассмотренное в предыдущем разделе данной лекции. Для исправления возникшей в программе ошибочной ситуации следует избавиться от различных вложенных блокировок.

Рассмотрим еще один пример мертвой блокировки, который возможен при работе программы, показанной в примере 5.6.

call omp_init_lock (LCKA)
c$omp parallel sections
c$omp section
      call omp_set_lock (LCKA)
      IVAL = dowork ()
      if (IVAL .EQ. TOL) then
         call omp_unset_lock (LCKA)
      else
         call error (IVAL)
      endif
c$omp section
      call omp_set_lock (LCKA)
      call use_B_and_A (RES)
      call omp_unset_lock (LCKA)
c$omp end sections
5.6. Пример возникновения мертвой блокировки в параллельной программе

В этом примере при выполнении параллельных потоков возможно возникновение как мертвой блокировки, так и условия состязательности. Мертвая блокировка возникает, если в первом параллельном потоке установлена блокировка объекта A, а IVAL не равно TOL. В этом случае установить блокировку объекта A во втором параллельном потоке невозможно, поскольку программа ждет отмены блокировки, установленной в первом параллельном потоке.

Если же в первом параллельном потоке установлена блокировка объекта A, а IVAL равно TOL, то в программе возникает условие состязательности.

Для исправления возникшей ситуации необходимо устранить не только мертвую блокировку, но и условия состязательности.

< Лекция 4 || Лекция 5: 123 || Лекция 6 >
Алексей Ищенко
Алексей Ищенко
Россия, г. Санкт-Петербург
Виолета Белянина
Виолета Белянина
Россия