Примитивы синхронизации
Last updated
Was this helpful?
Last updated
Was this helpful?
Изучаем Mutex, WaitGroup и Once с примерами
В данной статье кратко рассмотрим некоторые конструкции низкоуровневой синхронизации, которые наряду с горутинами и каналами предлагает нам один из самых популярных стандартных библиотечных пакетов Go, а именно . Таких конструкций очень много, а мы изучим лишь три из них, зато с примерами: WaitGroup
, Mutex
и Once
.
Примеры кода можно найти на . Поехали!
WaitGroup
используется для координации в случае, когда программе приходится ждать окончания работы нескольких горутин. Эта конструкция похожа на CountDownLatch
в Java. Обратимся к примеру.
Предположим, нам нужно вывести список всех файлов нашего домашнего каталога одновременно. Используем WaitGroup
для указания числа задач/горутин, завершения которых нам надо дождаться.
В данном случае оно совпадает с числом файлов/каталогов домашнего каталога. Используем Wait()
для блокировки, пока счётчик WaitGroup
не дойдёт до нуля.
Для выполнения этой программы понадобится:
Под каждую os.FileInfo
, которую мы находим в домашнем каталоге пользователя, создаётся горутина и при выводе её названия счётчик даёт отрицательное приращение с помощью этого Done
. Выполнение завершается после того, как программа пробежится по всему содержимому домашнего каталога.
Общий мьютекс — это блокировка с общим доступом, которая даёт возможность получать эксклюзивный доступ к тем или иным участкам кода. Далее в простом примере в функции incr
мы используем общую/глобальную переменную accessCount
.
Обратите внимание, что функция incr
защищена мьютексом
, поэтому только одна горутина может иметь к ней доступ. Мы бросаем на неё несколько горутин.
При выполнении результат здесь всегда будет один и тот же, т.е. Final = 500
(так как выполняются 500 итераций цикла for). Для выполнения программы понадобится:
Добавьте комментарий к следующим двум строчкам в функции incr
(или удалите эти строчки):
Запустите программу на локальном компьютере и снова выполните программу. Результат будет иной. Например, Final = 474
.
Позволяет определить задачу для однократного выполнения за всё время работы программы. Содержит одну-единственную функцию Do
, позволяющую передавать другую функцию для однократного применения. Вот вам пример:
Допустим, вы создаёте REST API с помощью пакета Go net/http
и хотите, чтобы участок кода выполнялся только после вызова обработчика HTTP-данных (например, для соединения с базой данных).
Используем в коде once.Do
: теперь можете быть уверены, что он выполнится только при первом вызове обработчика.
Вот как выглядит функция для однократного выполнения:
Видите этот once.Do(oneTimeOp)
? Вот что мы делаем внутри нашего HTTP-обработчика!
Запускаем код и получаем доступ к конечной точке REST.
И с другого терминала:
При первом доступе возврат функции будет немного медленным, и вы увидите следующие логи сервера:
При повторных запусках (сколько бы вы ни пытались) функция oneTimeOp
не выполнится. Для подтверждения проверьте логи.
Настоятельно рекомендую почитать о . Это особая разновидность захвата, предполагающая параллельное чтение (несколько читателей: когда к одним и тем же данным имеют доступ несколько читающих потоков или горутин, как в нашем случае) и синхронизированные записи (один писатель: когда к данным имеет доступ только записывающий поток или горутина).