Закрытие блокировки на строковое значение - PullRequest
0 голосов
/ 11 декабря 2018

У меня есть кусок кода в clojure, который должен работать изолированно.Допустим, эта функция

(defn isolate [string1])

Легко изолировать всю функцию на всех входах, вызывая ее так:

(def o (Object. ))

(locking o (isolate string1))

Однако это позволяет только один процесс / поток для одновременного доступа к изоляту.

Теперь я реализовал следующее:

(def current-locks (ref {}))

(defn mergeReverse [x y] (merge y x))

(defn merge-with-current-locks [key val]
  (dosync (alter current-locks mergeReverse {key val})))

(defn remove-lock [key]
  (dosync (alter current-locks dissoc key)))

и, наконец, блок потоков, вызывающий этот метод

(defn block-until-free [key val]
  (let [_ (merge-with-current-locks key val)]
    (if (dosync (and (contains? current-locks key)
                     (not= (get current-locks key) val)))
      (do
        (Thread/sleep 10)
        (block-until-free key val)))))

Как видно из решения, которое я использовалключи и значения здесь, и хотя я блокирую только ключи, но возможность использовать карты вместо массивов была полезной, поскольку я использовал свойство слияния, которое сливается в карту, только если карта не содержит это значение, и поскольку current-locks являетсяref Я использовал alter и поменял входы слияния для получения необходимого поведения.

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

Конечно, remove-lock должен вызываться после выполнения функции критической .

1 Ответ

0 голосов
/ 11 декабря 2018

Для этого вы должны использовать транзакцию базы данных.Вот пример кода Clojure:

  ; Wraps all commands in a single transaction
  (jdbc/with-db-transaction
    [tx db-conn]
    (let [clj-id (grab :id (only (jdbc/query tx ["select id from langs where lang='Clojure'"])))]
      (jdbc/insert-multi! tx :releases
                          [{:desc "ancients" :langId clj-id}
                           {:desc "1.8" :langId clj-id}
                           {:desc "1.9" :langId clj-id}]))
    (let [java-id (grab :id (only (jdbc/query tx ["select id from langs where lang='Java'"])))]
      (jdbc/insert-multi! tx :releases
                          [{:desc "dusty" :langId java-id}
                           {:desc "8" :langId java-id}
                           {:desc "9" :langId java-id}
                           {:desc "10" :langId java-id}])))

Здесь мы запрашиваем у таблицы langs значения id языков Clojure и Java.Затем мы добавляем строки в таблицу releases с столбцами desc и внешним ключом langId.Поскольку оба оператора insert-multi! переносятся через (jdbc/with-db-transaction, транзакция будет откатываться, если какой-либо другой поток обновит базу данных до ее завершения.

Приведенному выше коду потребуется цикл повторных попыток для перехвата исключения, если наша транзакция не удалась, а затем повторите попытку (возможно, со случайной задержкой).Вы можете найти весь пример кода здесь .


Обновление

Мой пример был для базы данных SQL, такой как Postgres.Я полагаю, что для Datomic вам понадобится такая функция, как db.fn/cas. См. Datomic docs для получения полной информации.Вы также можете задать в списке рассылки Datomic или опубликовать более конкретный вопрос Datomic в StackOverflow.

Для Postgres или Datomic транзакция будет прервана только в том случае, если конкретная строка / сущность, которую вы изменитетакже изменен другим потоком.Он не блокирует всю базу данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...