
Лекции / Лекция 2
.odtТехнологии и методы программирования. Лекция 2.
Синхронизация.
Чтение и запись из общей переменной делает результат неопределённым. Различные результаты при запуске программы с одними и теми же исходными данными — верный признак таких проблем.
Способы обеспечения синхронизации:
1) #pragma omp atomic – на параллельном участке в каждый момент времени может выполняться только 1 поток. При этом могут использоваться только бызовые двумерные операторы, а выражение, вычисляющее какую-либо переменную, не должно зависеть от самой этой переменной.
2) #pragma omp critical[]{оператор} – объявление критической секции
Распределение работ между потоками:
- по номерам. Программа узнаёт номер потока и действует так или иначе в зависимости от этого номера
- с помощью POSIX THREADS
Выполнение оператора ровно 1 раз на все потоки:
#pragma omp master – оператор выполняется только в главном потоке
#pragma omp single[опции]{оператор} – просто разовое выполнение.
Возможны опции:
- private – рассматривалось на первой лекции
- first_private – рассматривалось на первой лекции
- last_private – всем переменным из списка после завершения параллельного участка будут присвоены значения из последнего завершившегося потока.
- copyprivate[список] – после завершения оператора с single, значения из списка присваиваются переменным из списка private с тем же названием.
Важно:
При использовании single используется неявная барьерная синхронизация, все параллельные потоки приостанавливаются до завершения операторов single. При использовании опции nowait барьерная синхронизация отменяется, что делает эти опции несовместимыми.
Информационные зависимости.
Информационные зависимости — основное ограничение возможностей распараллеливания. Существуют зависимости 2 типов: по данным (2 оператора обращаются к одному полю в памяти, и, при этом, одно из обращений — запись) и по управлению (результат выполнения одного оператора влияет на выполнение другого). Примеры:
- а++; f(a); - зависимость по данным
- a++; if(a==10){b++; c=a} – зависимость по управлению
Разбиение программ на независимые блоки.
Разбиение необходимо, чтобы выполнить каждый из блоков в отдельном потоке. Разбиение осуществляется с помощью #pragma omp sections{} . В области действия этой директивы работают несколько секций, обозначаемых #pragma omp section. Section-блок может размещаться только в области действия директивы parallel.
Зависимость между итерациями цикла.
Зависимость присутствует, если 2 итерации обращаются к одним и тем же полям, и одно из обращений — запись. Такие итерации необходимо выполнять в порядке, определяемом зависимости. Нужно помнить, что компиляторы omp не проверяют наличие зависимости по данным, так как в спецификации этого не требуется.
Примеры распределения итераций цикла по потокам.
#pragma omp for
{
for (i=0; i<n; i++)
{
C[i]=A[i]+B[i];
}
}
#pragma omp for reduction(+, st)
{
for(i=0; i<n; i++)
{
st=A[i]*B[i];
}
}
Распределение итераций for может быть различно, и иметь разную эффективность. Для максимальной эффективности можно указывать вид распределения итераций по потокам явно, а не по умолчанию.