[МУЗЫКА] [МУЗЫКА] Здравствуйте! В этой лекции мы с вами рассмотрим модели памяти и классы переменных в OpenMp. Это очень важная тема, так как очень часто правильность работы параллельной программы зависит от того, к какому классу мы отнесли те или иные переменные. Модель данных в OpenMp предполагает наличие как общей памяти для всех потоков, так и локальной памяти для каждого отдельного потока. В связи с этим, в OpenMp существует 2 класса переменных. Первый класс – это общие переменные. Во многих директивах общие переменные можно объявить с использованием опции shared. Как можно догадаться из названия, общая переменная всегда существует лишь в одном экземпляре для всей области действия и доступна всем потокам под одним и тем же именем. Вот самый простой пример применения данной опции: мы объявили переменную типа string вне параллельной области и присвоили ей значение: "Hello, world". Далее объявили эту переменную общей для всех потоков. И каждый поток вывел то, что было в текстовой строке. В результате мы увидим сообщение столько раз, сколько у нас будет потоков выполнять параллельную секцию. С общими переменными нужно быть очень аккуратными. Если несколько потоков одновременно пишут в общую переменную без синхронизации, или какой-то поток читает общую переменную, а другой в эту переменную пишет, опять же без синхронизации, то в данном случае возникает ситуация гонки данных, или по английски – data race. В данном случае итог выполнения программы непредсказуем. И следующий пример это демонстрирует. В программе мы объявили переменную x общей. И создали параллельную область с 30-ю потоками. Каждый поток к переменной x добавляет 1. Если 30 раз к переменной x добавить 1, то в конце должно получиться 30. Если вы несколько раз будете запускать программу, то иногда вы будете получать правильный результат, а иногда – нет. Это все из-за гонки данных. Чтобы программа работала правильно, в данном случае требуется синхронизировать доступ к общей переменной. Как это делать, мы будем рассматривать в следующих лекциях. Давайте теперь рассмотрим второй класс. Это локальные или приватные переменные потока. Во многих директивах приватные переменные можно объявить с использованием опции private. Объявление локальной переменной вызывает порождение своего экземпляра данной переменной того же типа и размера для каждого потока. Изменение потоком значения своей локальной переменной никак не влияет на изменение значения этой же локальной переменной в других потоках. Начальное значение локальной копии переменных не определено, не забывайте об этом. Давайте рассмотрим пример, демонстрирующий использование опции private. В данном примере переменная n объявлена как локальная переменная в параллельной области. Сначала присваиваем переменной n значение 1. Затем значение n будет выведено в последовательной области. Вы увидите на экране 1. Далее будет создана параллельная секция, и переменная n объявлена как приватная. Это значит, что каждый поток будет работать со своей копией переменной n. При этом в начале параллельной области в каждом потоке переменная n не будет инициализирована. Все потоки выведут значения своей копии переменной n в начале параллельной области. Неинициализированное значение может зависеть от реализации компилятора. Далее все потоки присвоят переменной n свой порядковый номер, полученный с помощью функции omp_get_thread_nun. И выведут переменную n. После завершения параллельной области будет еще раз выведено значение переменной n, которое окажется равным 1. Как видите, оно не изменилось во время выполнения параллельной области. Есть еще несколько опций, которые позволяют изменить поведение приватных переменных. Вместо опции private можно воспользоваться следующими опциями. Опция firsprivate задает список переменных, для которых порождается локальная копия в каждом потоке; локальные копии переменных инициализируются значениями этих переменных в потоке-мастере. Опция lastprivate – переменным, перечисленным в списке, присваивается результат с последнего витка цикла. И опция reduction – задает оператор и список переменных; для каждой переменной создается локальная копия в каждом потоке; над локальными копиями переменных после выполнения всех операторов параллельной области выполняется заданный оператор; операторы для языка Си – это арифметический или логический оператор. Рассмотрим пример, демонстрирующий использование опции firsprivate. Переменная n объявлена как firsprivate в параллельной области. Значение n будет выведено в 4-х разных местах. Первый раз значение n будет выведено в последовательной области, сразу после инициализации. Второй раз все потоки выведут значение своей копии переменной n в начале параллельной области. И это значение будет равно 1. Далее с помощью функции omp_get_thread_num все потоки присвоят переменной n свой порядковый номер и еще раз выведут значения n. В последовательной области будет еще раз выведено значение n, которое снова окажется равным 1. И остался еще один вопрос, который, возможно, у кого-то из вас уже появился. А какого класса будет переменная, если не включить одну из них в опции? По умолчанию все переменные, порожденные вне параллельной области, при входе в эту область остаются общими. Исключение составляют переменные, являющиеся счетчиками итераций в цикле. Переменные, порожденные внутри параллельной области, по умолчанию являются локальными или private. Явно назначить класс переменных по умолчанию можно с помощью опции default. Не рекомендуется постоянно полагаться на правило по умолчанию. Для большей надежности лучше всего явно описывать классы используемых переменных, указывая в директивах OpenMp опции: private, shared, firsprivate, lastprivate и reduction. Итак, в этой лекции мы с вами узнали, какие классы переменных бывают в OpenMp. И как всегда, небольшой вопрос в конце лекции. Посмотрите на эту программу, попытайтесь без выполнения данной программы ответить, какое значение переменной будет выведено на экран. Ну а в следующей лекции мы с вами его более подробно разберем. До встречи!