Совместное использование ресурсов или управление количеством экземпляров с помощью async-mutex

Среда выполнения JavaScript является однопоточной, но асинхронный характер обещаний приводит к необходимости синхронизировать доступ к ресурсам в определенных случаях использования.

Обработчики событий - это асинхронные события, которым может быть полезно взаимное исключение. Такие события, как обработчики прокрутки и нажатия кнопок, могут запускать несколько событий в течение секунды, но иногда вам нужно только первое событие (а остальные нежелательны). Нам нужен способ гарантировать порядок асинхронных событий.

Об асинхронном мьютексе

async-mutex - это пакет npm, который реализует примитивы синхронизации, позволяя детерминированно управлять асинхронными событиями.

Синхронизирующие примитивы - Mutex и Semaphore.

  • Mutex (сокращение от взаимного исключения) - это блокировка. Как только кто-то получит блокировку, любые другие попытки получить ее будут заблокированы до тех пор, пока владелец блокировки не освободит ее!
  • Sempahore похож на Mutex, за исключением того, что он может позволить нескольким людям "приобретать" его. Это полезно в случаях, когда вы можете разрешить до X асинхронных событий одновременно.

async-mutex также предоставляет декоратор withTimeout. Таким образом, когда вы обертываете мьютекс или семафор с withTimeout украшением, вы можете указать максимальное время блокировки для блокировки, прежде чем запрашивающая сторона будет отклонена.

Вы можете установить async-mutex с помощью следующей команды

npm install --save async-mutex

После установки вы можете импортировать объекты, представленные async-mutex

import { Mutex, Semaphore, withTimeout } from 'async-mutex';

Готовый? давайте начнем изучать использование этих синхронизирующих примитивов. Сначала я представлю наш пример. После этого я покажу примеры правильного взаимного исключения с помощью Mutex и Semaphore.

Заказанный счетчик сервера

Приведенный выше пример - это заказанный счетчик сервера, который мы хотим построить. При нажатии на кнопку:

  • Асинхронный запрос отправляется на сервер
  • Сервер увеличивает внутренний счетчик
  • Сервер возвращает текущее значение счетчика
  • Клиент (наша веб-страница) отображает счетчик в тосте, показанном выше.

Важно, чтобы порядок номеров сохранялся при получении. Итак, как мы можем синхронизировать порядок запросов? Мы знаем, что сетевые запросы асинхронны. Вполне возможно, что поступление первого запроса займет больше времени, чем второго, или второго запроса - больше, чем третьего запроса, и так далее. Это неупорядоченное поступление запросов будет проблемой для нашего приложения.

Подход «без синхронизации»

Что бы произошло, если бы мы разработали это приложение без конструкций синхронизации? Вот пример реализации моделирования этого приложения без синхронизации.

Сервер моделируется с помощью processCommmand(), а задержки сети моделируются с помощью serverDelay(). Поскольку механизмов синхронизации нет, при нажатии кнопки «Нажми меня» сразу же запускается новый запрос.

Это результат этой реализации.

Ой ой - числа, как и ожидалось, отображаются не по порядку, и мы не смогли сделать наше приложение для отображения чисел по порядку.

Решение проблемы с выходом из строя с помощью Mutex

Проблема в том, что сетевые запросы не работают, но наше приложение хочет, чтобы они были в порядке. Один из способов решить эту проблему - использовать блокировку Mutex, чтобы разрешить обработку только одного запроса за раз, блокируя другие запросы, пока не наступит их очередь.

Вот реализация с Mutex.

Последовательность использования Mutex API следующая:

  • Строка 23: инициирован запрос на получение clientLock . Этот запрос будет заблокирован, если кто-то другой уже получил блокировку и еще не снял ее.
  • Строка 33: клиентская блокировка снимается после того, как сервер ответил, и мы показали тост. Это позволяет другим событиям нажатия кнопки теперь конкурировать за блокировку и инициировать свой сетевой запрос сервера!

Этот механизм блокировки гарантирует, что одновременно будет обрабатываться только одно событие кнопки, а остальные блокируются и ставятся в очередь. Теперь мы достигли намеченной упорядоченной реализации нашего исходного примера, показанного в начале!

Ограничение количества видимых тостов

Что, если вам не нравится, что на экран может выводиться сразу несколько тостов? Мы можем расширить нашу логику, чтобы ограничить количество одновременно отображаемых тостов. Наша конструкция синхронизации здесь будет семафором!

Думайте о Sempahore как о мьютексе, но он может позволить нескольким асинхронным запросам выполнять часть кода за раз. Вы также можете настроить максимальное количество!

Используя мьютекс и семафор, я смог ограничить количество тостов на экране до 2 за раз.

А вот код, связанный с приведенным выше примером.

Строка 6

Структура Sempahore инициализируется значением 2, которое указывает, что одновременно могут отображаться не более 2 тостов.

Строки 26–31

Наша семафорная логика приходит нам на помощь, когда мы хотим отобразить тост. Мы пытаемся приобрести Семпахор. Если нам это удалось, мы создаем объект Toast, а затем передаем releaseSemaphore() функции completeCallback Toast. Этот обратный вызов вызывается, когда тост отклоняется.

Вот и все! Используя Mutex и Semaphore, вы можете синхронизировать асинхронный код в своих программах!