TL;DR: https://github.com/kifi/ReactiveLock

В Kifi мы стараемся максимально следовать реактивным моделям. За последние два года мы оценили сильные стороны шаблона, но также столкнулись с некоторыми недостатками. Этот пост и сопровождающая его библиотека с открытым исходным кодом посвящены двум конкретным проблемам, которые, как мы обнаружили, недостаточно хорошо решаются существующими библиотеками или функциями языка:

  1. Контроль параллелизма, т. е. ограничение параллелизма частей кода.
    Во многих случаях эта необходимость может возникнуть, особенно в контексте конкуренции за ресурсы. Сценарий, в котором это, пожалуй, наиболее критично для нас, — это межсервисные вызовы в нашей SOA. Дешёвый запрос, но дорогой в исполнении — обычное явление в SOA, и, по сути, мы хотим предотвратить случайный запуск одной службой другой.
    Конечно, в языке есть конструкции, позволяющие ограничить параллелизм, например, акторная система или специальный контекст выполнения с несколькими потоками (и мы тоже используем их, когда это уместно), но если все, что вы хотите сделать, это «предотвратить более чем N потоков от выполнения этой функции одновременно» это кажется немного тяжелым. Очевидно, что классические блокировки (и их собратья) делают это действительно простым, но у них есть очень нежелательное свойство блокировать потоки. Особенно это плохо в реактивной среде, когда вы можете быстро истощить пул потоков.
    Нам нужно было решение, которое было бы легким в синтаксисе, например блокировках, без блокировки потоков.
  2. Переход от классических шаблонов резьбы/замка. При переходе от более традиционных шаблонов параллелизма к реактивному программированию на самом деле нет хорошего способа сделать это постепенно. Мы хотели сделать рефакторинг меньше и, следовательно, более управляемым. Это действительно относится как к коду, так и к наборам навыков инженеров.

Чтобы решить эти проблемы, мы создали компонент, который мы называем реактивной блокировкой.

В своей простейшей форме использование ReactiveLock во многом похоже на старую добрую конструкцию synchronized.

Вызовы foo немедленно возвращаются с Future, однако только 2 из этих Future будут фактически выполняться одновременно.

Существует еще одна конструкция для работы со сценарием, в котором у вас уже есть Future[_] (или =› Future[_]) и несколько служебных функций для мониторинга, но это почти все. Вы можете найти более подробную информацию об использовании и весь исходный код на github.

Комментарии и дополнения очень приветствуются.

Мы написали этот пост во время работы над Kifi — соединяем людей с помощью знаний. Подробнее.

Первоначально опубликовано на eng.kifi.com 19 мая 2015 г.