Нижегородский государственный университет им. Н.И.Лобачевского
Опубликован: 02.10.2012 | Доступ: свободный | Студентов: 1754 / 201 | Длительность: 17:47:00
Специальности: Программист
Лекция 4:

Введение в технологии параллельного программирования (OpenMP)

4.4.2. Совместная обработка локальных переменных (операция редукции)

Использование параметра lastprivate позволяет сохранить значения локальной переменной одного из потоков, но во многих случаях для обработки могут понадобиться значения всех локальных переменных. Данная возможность может быть обеспечена, например, сохранением этих значений в общих переменных – более подробно правила работы с общими переменными будет рассмотрена в следующем разделе. Другой подход состоит в использовании коллективных операций над локальными переменными, предусмотренными в OpenMP. Задание коллективной операции происходит при помощи параметра reduction директивы for:

reduction (operator: list)
      

где список list задает набор локальных переменных (повторное описание в параметре private переменных из списка list не требуется), для которых должна быть выполнена коллективная операция, а поле operator указывает тип этой операции. Допустимыми значениями для поля operator являются следующие операции (которые не могут быть перегружены):

+, -, *, &, |, ^, &&, ||

Операция редукции обычно применяется к переменным, для которых вычисления задаются в виде выражений следующего вида:

  • x = x <operator> <expression>
  • x = <expression> <operator> x (за исключением операции вычитания)
  • x <op>= <expression>
  • x++, ++x, x--, --x

где x есть имя скалярной переменной, выражение expression не должно включать переменную x, возможные операции для поля operator совпадают с вышеприведенным списком, а допустимыми значениями для поля op являются операции:

+, -, *, &, |, ^

В качестве примера можно добавить в нашу учебную задачу действие по сложению всех сумм элементов строк матрицы – возможный программный код может быть следующим:

total = 0; 
#pragma omp parallel for shared(a) \ 
private(i,j,sum) reduction (+:total) 
{ 
  for (i=0; i < NMAX; i++) { 
    sum = 0; 
    for (j=i; j < NMAX; j++) 
      sum += a[i][j]; 
    printf ("Сумма строки %d равна %f\n",i,sum); total = total + sum; 
} /* Завершение параллельного фрагмента */ 
printf ("Общая сумма матрицы равна %f\n",total);
      
4.7. Пример использования операции редукции данных

В качестве пояснений добавим, что по параметру reduction для переменной total создаются локальные переменные, затем полученные значения в потоках суммируются и запоминаются в исходной переменной. Подчеркнем, что использование общей переменной total без создания локальных копий для накопления общей суммы при использовании в потоках операции

total = total + sum;

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

4.5. Организация взаимоисключения при использовании общих переменных

Как уже неоднократно отмечалось ранее, при изменении общих данных несколькими потоками должны быть обеспечены условия взаимоисключения – изменение значений общих переменных должно осуществляться в каждый конкретный момент времени только одним потоком. Рассмотрим возможные способы организации взаимоисключения.

4.5.1. Обеспечение атомарности (неделимости) операций

Действие над общей переменной может быть выполнено как атомарная (неделимая) операция при помощи директивы atomic. Формат директивы имеет вид:

#pragma omp atomic 
<expression>
      

где expression должно иметь вид:

x++; или ++x; или x--; или --x;

где x есть имя любой целой скалярной переменной. Директива atomic может быть записана и в виде:

#pragma omp atomic 
        x <operator>= <expression>
      

где x есть имя скалярной переменной, выражение expression не должно включать переменную x, а допустимыми значениями для поля operator являются следующие операции (которые не могут быть перегружены):

+, *, -, /, &, |, ^, >>, <<
      

Как следует из названия, операция директивы atomic выполняется как неделимое действие над указанной общей переменной, и, как результат, никакие другие потоки не могут получить доступ к этой переменной в этот момент времени.

Применим рассмотренную директиву для нашей учебной задачи при вычислении общей суммы:

total = 0;
#pragma omp parallel for shared(a) \
private(i,j,sum)
{
for (i=0; i < NMAX; i++) {
  sum = 0;
  for (j=i; j < NMAX; j++)
    sum += a[i][j];
  printf ("Сумма строки %d равна %f\n",i,sum);
#pragma omp atomic
  total = total + sum;
} /* Завершение параллельного фрагмента */
printf ("Общая сумма матрицы равна %f\n",total);

      
4.8. Пример использования директивы atomic

Отметим, что в данном случае потоки работают непосредственно с общей переменной total. Как можно видеть, директива atomic может быть применена только для простых выражений, но является наиболее эффективным средством организации взаимоисключения, поскольку многие из допустимых для директивы операций на самом деле выполняются как атомарные на аппаратном уровне. Тем не менее, следует отметить, что данный вариант программы в общем случае будет проигрывать варианту 5.6 по эффективности, поскольку теперь синхронизация потоков будет выполняться для каждой строки обрабатываемой матрицы, а в примере 5.6 количество моментов синхронизации ограничено числом потоков.

4.5.2. Использование критических секций

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

Определение критической секции в OpenMP осуществляется при помощи директивы critical, формат записи которой имеет вид:

#pragma omp critical [(name)] <block>

Как можно заметить, критические секции могут быть именованными – можно рекомендовать активное использование данной возможности для разделения критических секций, т. к. это позволит уменьшить число блокировок процессов.

Покажем использование механизма критических секций на примере нахождения максимальной суммы элементов строк матрицы. Одна из возможных реализаций состоит в следующем:

smax = -DBL_MAX;
#pragma omp parallel for shared(a) \
private(i,j,sum)
{
  for (i=0; i < NMAX; i++) {
    sum = 0;
    for (j=i; j < NMAX; j++)
      sum += a[i][j];
  printf ("Сумма строки %d равна %f\n",i,sum);
  if ( sum > smax )
#pragma omp critical
  if ( sum > smax )
    smax = sum;
} /* Завершение параллельного фрагмента */
printf ("Максимальная сумма равна %f\n",smax);
        
      
4.9. Пример использования критических секций

Следует обратить внимание на реализацию проверки суммы элементов строки на максимум. Директиву critical можно записать до первого оператора if, однако это приведет к тому, что критическая секция будет задействована для каждой строки и это приведет к дополнительным блокировкам потоков. Лучший вариант – организовать критическую секцию только тогда, когда необходимо осуществить запись в общую переменную smax (т. е. когда сумма элементов строки превышает значение максимума). Отметим особо, что после входа в критическую секцию необходимо повторно проверить переменную sum на максимум, поскольку после первого оператора if и до входа в критическую секцию значение smax может быть изменено другими потоками. Отсутствие второго оператора if приведет к появлению трудновыявляемой ошибки, учет подобных моментов представляет определенную трудность параллельного программирования.

4.5.3. Применение переменных семафорного типа (замков)

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

  • Инициализировать замок:
    void omp_init_lock(omp_lock_t *lock);
  • Установить замок:
    void omp_set_lock (omp_lock_t &lock);
    Если при установке замок был установлен ранее, то поток блокируется.
  • Освободить замок:
    void omp_unset_lock (omp_lock_t &lock);
    После освобождения замка при наличии блокированных на этом замке потоков один из них активизируется и замок снова отмечается как закрытый.
  • Установить замок без блокировки:
    int omp_test_lock (omp_lock_t &lock);
    Если замок свободен, функция его закрывает и возвращает значение true. Если замок занят, поток не блокируется, и функция возвращает значение false.
  • Перевод замка в неинициализированное состояние:
    void omp_destroy_lock(omp_lock_t &lock)
    Переработаем пример 5.9 так, чтобы для организации взаимного исключения при доступе к общим данным использовался механизм замков:
omp_lock_t lock;
omp_init_lock(&lock);
smax = -DBL_MAX;
#pragma omp parallel for shared(a) \
private(i,j,sum)
{
for (i=0; i < NMAX; i++) {
  sum = 0;
  for (j=i; j < NMAX; j++)
    sum += a[i][j];
  printf ("Сумма строки %d равна %f\n",i,sum);
  if ( sum > smax ) {
    omp_set_lock (&lock);
    if ( sum > smax )
      smax = sum;
    omp_unset_lock (&lock);
    }
} /* Завершение параллельного фрагмента */ 
printf ("Максимальная сумма равна %f\n",smax); 
omp_destroy_lock (&lock);
      
Листинг 4.10. Пример организации взаимоисключения при помощи замков

В OpenMP поддерживаются также и вложенные замки, которые предназначены для использования в ситуациях, когда внутри критических секций осуществляется вызов одних и тех же замков. Механизм работы с вложенными замками является тем же самым (в наименование функций добавляется поле nest), т. е. их использование обеспечивается при помощи функций:

void omp_init_nest_lock(omp_nest_lock_t *lock);
void omp_set_nest_lock (omp_nest_lock_t &lock);
void omp_unset_nest_lock (omp_nest_lock_t &lock);
int omp_test_nest_lock (omp_nest_lock_t &lock);
void omp_destroy_nest_lock(omp_nest_lock_t &lock)
      

Как можно заметить, для описания вложенных замков используется тип omp_nest_lock_t.

Дмитрий Остапенко
Дмитрий Остапенко

поддерживаю выше заданые вопросы

 

Павел Каширин
Павел Каширин

Скачал архив и незнаю как ничать изучать материал. Видео не воспроизводится (скачено очень много кодеков, различных плееров -- никакого эффекта. Максимум видно часть изображения без звука). При старте ReplayMeeting и Start в браузерах google chrome, ie возникает script error с невнятным описанием. В firefox ситуация еще интереснее. Выводится: 

Meet Now: Кукаева Светлана Александровна. 

Meeting Start Time: 09.10.2012, 16:58:04
Meeting Stop Time: 09.10.2012, 18:45:18
Recording Duration:01:47:14

Downloading...

Your Web browser is not configured to play Windows Media audio/video files.

Make sure the features are enabled and available.