|
||||||||||||||||||||||||||
Семинар 10. Тема: Технология OpenМР: работа с общими и локальными даннымиСеминар 10 Тема: Технология OpenМР: работа с общими и локальными данными
По итогам прошлого семинара – знаем, что в OpenMP-программе работу начинает одна нить (нить-мастер), с помощью прагмы omp parallel организуются параллельные блоки, выполняемые набором потоков (нитей), количество которых можно регулировать, каждая из нитей имеет свой номер и нить-мастер имеет номер 0. Взаимосвязь между нитями в параллельных секциях, а также между параллельными (многопоточными) и последовательными (однопоточными) участками кода поддерживается за счет различных типов данных. В OpenMP существуют как общие, так и локальные переменные. Необходимо учитывать особенности работы с каждым типом, в том числе тот факт, что доступ у каждой нити к общей переменной и соответственно возможность изменить ее значение, в случае некорректного использования общих переменных может привести к неправильной работе программы. В OpenMP определены следующие основные типы переменных, характеризующие доступ к ним из разных участков OpenMP-программы (эти типы объявляются в прагмах parallel; не путать со стандартными типами данных int, float и т.п.): · shared – такие переменные являются общими для всех нитей, т.е. все нити во всех параллельных и последовательных областях кода будут обращаться одной области памяти; · private – каждый поток имеет свою копию переменной, значение которой обновляется независимо каждой нитью, не передается из последовательной области параллельную и не сохраняется за пределами параллельной области; · firstprivate – аналогична private, но переменные данного типа при входе в параллельный регион инициализируются значением, которое переменная имела перед открытием этого региона; · lastprivate - аналогична private, но после закрытия параллельной области переменные сохраняют значение, полученное в ходе вычислений внутри параллельного региона. Используется в конструкциях for и section. На кластере HybriLIT типом переменной по умолчанию является shared. В таблице показано, как ведут себя переменные типа shared, private, firstprivate при переходе из последовательной области в параллельную и обратно.
Пример 1: Задаем целочисленную переменную а – private, значение которой, как в таблице, задается в начале программы, когда работает только одна нить, нить-мастер. Пусть a =199, как в таблице. Выводим на дисплей значения этой переменной: - в мастер-нити; - в начале параллельной секции, - в конце параллельной секции после некоторых вычислений; - после закрытия параллельной секции. Будем следить – как изменится (или не изменится) значение а.
Что наблюдаем при запуске этой программы (интерактивный запуск с числом нитей 6 по умолчанию): Пример 2: Объявим теперь в нашем предыдущем примере переменную а – firstprivate: #pragma omp parallel firstprivate(a) Снова проследим, как будет меняться (или не изменится) ее значения при открытии и закрытии параллельной области. Видно, что при входе в параллельную область в каждой из шести нитей переменная а имеет значение а=199, заданное в мастер-нити до открытия паралельной области. Далее, каждая нить вычисляет свое собственное значение а, равное ее номеру, и выводит это значение на печать. Однако после закрытия параллельной области результаты этих расчетов не сохраняются. По-прежнему имеем в мастер-нити а=199, как и в предыдущем примере.
Пример 3: Теперь пусть наша переменная а – общая (shared): #pragma omp parallel shared(a) Снова проследим, как будет меняться (или не изменится) ее значение при открытии и закрытии параллельной области. Поскольку переменная общая и все нити в параллелньой области имеют к ней равноправный доступ – при входе в паралельную область каждая нить печатает а=199, далее каждая нить увеличивает значение общей для всех переменной на величину, равную своему номеру, так что на печати наблюдаем постепенное увеличение значения а. После закрытия параллельной области имеем: а=199+0+1+2+3+4+5 = 214. Однако – не все так просто! Все нити работают с единым адресом в памяти, т.е. каждая нить копирует значение а в свой кэш, вычисляет новое значение и обновляет а в своем кэше и в общей памяти. Поскольку все нити работают параллельно – существует вероятность, что две (или более) нити реализуют запрос к памяти практически одновременно и скопируют в свой кэш одно и то же значение а, которое было на тот момент сохранено в памяти. Каждая их этих нитей увеличит скопированное значение а на свой номер и затем обновит содержимое общего адреса памяти. В результате окажется, что одна (или более) нитей записали свой результат «поверх» результата, записанного чуть ранее другой нитью, так что не все результаты вычислений окажутся учтены в итоговом значении а. Из-за этого финальное значение переменной а может меняться от запуска к запуску. Для того, чтобы убедиться в этом – запустим программу несколько раз подряд:
Видно, что в разных запусках финальное значение а – разное: вместо а=199 может получаться а=212, а=210 и т.п. Для наглядности в этой программе закомментирован вывод на печать внутри параллельной секции:
Таким образом, работа с общими данными требует внимательности и аккуратности. «Разруливать» ситуации, подобные представленной в примере 3, помогают директивы синхронизации нитей и опция редукции, которые будем изучать на следующих семинарах.
Задание
1. Изучить раздел 2.1 учебного пособия по OpenMP. 2. Воспроизвести на кластере Hybrilit вышеприведенные примеры по работе с локальными и общими данными, убедившись, что объясненные выше закономерности поведения соответствующих переменных сохраняются. 3. Составить и запустить программу, выполняющую следующее. В мастер-нити объявляются целочисленные переменные a=1, b=10, c=100. Пусть c – private, b – shared, a – firstprivate. Пусть в параллельной области производятся следующие действия: c=a+id; b+=a; а=22. Нужно вывести на печать значения a, b, c после закрытия параллельной области и объяснить полученный результат.
|
||||||||||||||||||||||||||
|