[ЗАСТАВКА] [ЗАСТАВКА] Здравствуйте! В этой лекции мы с вами рассмотрим более сложный механизм синхронизации работ, позволяющий реализовать большее число алгоритмов и предоставляющий вам как программистам больше свободы действий. Один из вариантов синхронизаций в OpenMP реализован через механизм замков. В качестве замков используются общие целочисленные переменные. Размер переменных должен быть достаточным для хранения адреса. Данные переменные должны использоваться только как параметры примитивов синхронизации. Замок может находиться в одном из трех состояний: неинициализированный, разблокированный или заблокированный. Разблокированный замок может быть захвачен некоторым потоком, при этом он переходит в заблокированное состояние. Поток, захвативший замок, и только он, может его освободить, после чего замок возвращается в разблокированное состояние. Есть два типа замков: простые замки и множественные замки. Множественный замок может многократно захватываться одним потоком перед его освобождением, в то время как простой замок может быть захвачен только однажды. Для множественного замка вводится понятие коэффициента захваченности (nesting count). Изначально он устанавливается в 0. При каждом следующем захватывании увеличивается на единицу, а при каждом освобождении уменьшается на единицу. Множественный замок считается разблокированным, если его коэффициент захваченности равен 0. Для инициализации простого или множественного замка используются соответственно функции: omp_init_lock и omp_init_nest_lock. После выполнения функций замок переводится в разблокированное состояние. Для множественного замка коэффициент захваченности устанавливается в 0. Функции omp_destroy_lock и omp_destroy_nest_lock используются для переведения простого или множественного замка в неинициализированное состояние. Для захватывания замка используются функции omp_set_lock и omp_set_nest_lock. Вызвавший эту функцию поток дожидается освобождения замка, а затем захватывает его. Замок при этом переводится в заблокированное состояние. Если множественный замок уже захвачен данным потоком, то поток не блокируется, а коэффициент захваченности увеличивается на единицу. Для освобождения замка используются функции omp_unset_lock и omp_unset_nest_lock. Вызов этой функции освобождает простой замок, если он был захвачен вызвавшим потоком. Для множественного замка уменьшается на единицу коэффициент захваченности. Если коэффициент станет равен 0, замок освобождается. Если после освобождения замка есть потоки, заблокированные на операции, захватывающие данный замок, замок будет сразу же захвачен одним из ожидающих потоков. Рассмотрим пример, иллюстрирующий применение технологии замков. Переменная lock используется для блокировки. В последовательной области производится инициализация данной переменной с помощью функции omp_init_lock. В начале параллельной области каждый поток присваивает переменной n свой порядковый номер. После этого с помощью функции omp_set_lock один из потоков выставляет блокировку, а остальные потоки ждут, пока поток, вызвавший эту функцию, не снимет блокировку с помощью функции omp_unset_lock. Все потоки по очереди выведут сообщения: "Начало заблокированной секции" и "Конец заблокированной секции". При этом между двумя сообщениями от одного потока не могут встретиться сообщения от другого потока. В конце с помощью функции omp_destroy_lock происходит освобождение переменной lock. Для неблокирующей попытки захвата замка используются функции omp_test_lock и omp_test_nest_lock. Данная функция пробует захватить указанный замок. Если это не удалось, то для простого замка функция возвращает единицу, а для множественного замка — новый коэффициент захваченности. Если замок захватить не удалось, в обоих случаях возвращается 0. Способы применения механизма замков очень разнообразны. Вы можете организовывать сложные критические секции, контролировать доступ к общим переменным и многое другое. Все зависит от того, какую задачу вы решаете. Давайте теперь подведем итоги третьего модуля. В этом модуле мы рассмотрели директивы, позволяющие распределить работу между потоками, а также директивы, которые позволяют синхронизировать работу потоков и организовать доступ к общим переменным. В следующем модуле мы рассмотрим вопросы оптимизации программ, а в частности, векторные вычисления. До встречи!