[БЕЗ_ЗВУКА] [ЗАСТАВКА] Здравствуйте. В этой лекции рассмотрим как можно синхронизировать выполнение между потоками. Для этого в OpenMP предназначен целый набор директив. Самый распространенный способ синхронизации в OpenMP — это барьерная синхронизация. Он оформляется с помощью директивы barrier. Потоки, выполняющие текущую параллельную область, дойдя до этой директивы, останавливаются и ждут, пока все потоки не дойдут до этой точки программы. После чего разблокируются и продолжают работать дальше. Кроме того, для разблокировки необходимо, чтобы все синхронизируемые потоки завершили все порожденные ими задачи, созданные с помощью директивы tasks, которую рассматривали в прошлой лекции. Рассмотрим пример, демонстрирующий применение директивы barrier. Директива barrier используется для упорядочивания вывода от работающих потоков. Если вы будете несколько раз запускать программу, то увидите, что выдачи с разных потоков "Сообщение 1" и "Сообщение 2" могут перемежаться в произвольном порядке, а выдача "Сообщения 3" со всех потоков всегда будет строго после двух предыдущих выдач. Это из-за того, что потоки выполняют строку, выводящую "Сообщение 3" только тогда, когда все дойдут до директивы barrier. Следующая директива синхронизации — ordered. Про нее я уже упоминал на первой лекции данного модуля, когда говорил про опции директивы for. Директива ordered определяет блок внутри тела цикла, который должен выполняться в том порядке, в котором итерации идут в последовательном цикле. Блок операторов относится к самому внутреннему циклу, а в параллельном цикле должна быть задана опция ordered. Поток, выполняющий первую итерацию цикла, выполняет операции данного блока. Поток, выполняющий любую следующую итерацию должен должен сначала дождаться выполнения всех операций блока всеми потоками, выполняющими предыдущие итерации. Пример иллюстрирует применение директивы ordered и опции ordered. Цикл for помечен как ordered, внутри тела цикла идут две выдачи. Одна вне блока ordered, а вторая внутри него. В результате, первая выдача получается неупорядоченной, а вторая идет в строгом порядке по возрастанию номера итерации. Что же, двинемся дальше и рассмотрим следующую директиву. С помощью директив critical оформляется критическая секция программ. В каждый момент времени в критической секции может находиться не более одного потока. Если критическая секция уже выполняется каким-либо потоком, то все другие потоки, выполнившие директиву для секции с данным именем будут заблокированы, пока вошедший поток не закончит выполнение данной критической секции. Как только работающий поток выйдет из критической секции, один из заблокированных на входе потоков войдет в нее. Если на входе в критическую секцию стояло несколько потоков, то случайным образом выбирается один из них. А остальные заблокированные потоки продолжают ожидание. Все неименнованные критические секции условно ассоциируются с одним и тем же именем. Все критические секции, имеющие одно и то же имя, рассматриваются единой секцией, даже если находятся в разных параллельных областях. Поэтому критическим секциям лучше давать имена и следить за тем, какие критические секции где использованы. Побочные входы и выходы из критической секции запрещены. Следующий пример иллюстрирует применение директивы critical. Переменная n объявлена вне параллельной области. Поэтому по умолчания является общей. Критическая секция позволяет разграничить доступ к переменной n. Каждая нить по очереди присвоит n свой номер и затем напечатает полученное значение и выведет его на экран. Если бы в примере не была указана директива critical, результат выполнения программы был бы непредсказуемым. С директивой critical порядок вывода результатов может быть произвольным. Но это всегда будет набор одних и тех же чисел от 0 до n–1, где n — это количество потоков, выполняющих параллельную область. Конечно, подобного же результата можно было бы добиться другими способами. Например, объявив переменную n локальной. Тогда каждый поток работал бы со своей копией этой переменной. Однако в исполнении этих фрагментов разница существенная. Если есть критическая секция, то в каждый момент времени фрагмент кода будет выполняться лишь одним потоком. Остальные потоки, даже если они уже подошли к данной точке программы и готовы к работе, будут ожидать своей очереди. Если критической секции нет, то все потоки могут одновременно выполнить данный участок кода. С одной стороны, критические секции предоставляют удобный механизм для работы с общими переменными, но с другой стороны, пользоваться им нужно осмотрительно, поскольку критические секции добавляют последовательные участки кода в параллельную программу, что может снизить ее эффективность. Частным случаем использования критических секций на практике является обновление общих переменных. Например, если переменная sum является общей и оператор вида sum присвоит sum + некоторое выражение (expression) находится в параллельной области программы, то при одновременном выполнении данного оператора несколькими потоками, можно получить некорректный результат. Чтобы избежать такой ситуации можно воспользоваться механизмом критических секций. Или специально предусмотренной для таких случаев директивой atomic. Данная директива относится к идущему непосредственно за ней оператору присваивания, гарантируя корректную работу с общей переменной, стоящей в его левой части. На время выполнения оператора блокируется доступ к данной переменной всем запущенным в данный момент потокам, кроме потока, выполняющего операцию. Атомарной является только работа с переменной в левой части оператора присваивания, при этом вычислениям в правой части не обязательно быть атомарными. Рассмотрим пример, иллюстрирующий применение директивы atomic. В данном примере производится подсчет общего количества порожденных потоков. Для этого каждый поток увеличивает на единицу значение переменной count, являющейся общей. Для того, чтобы предотвратить одновременное изменение несколькими потоками значения переменной, стоящей в левой части оператора присваивания, используется директива atomic. В этой лекции мы рассмотрели наиболее распространенные способы синхронизации выполнения работы потоков. А в следующей лекции рассмотрим более сложный способ синхронизации, но позволяющий реализовать большее количество параллельных алгоритмов. До встречи!