【问题标题】:Clojure locking on string valueClojure 锁定字符串值
【发布时间】:2018-12-11 16:31:07
【问题描述】:

我在 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-locksref 我使用了 alter 并交换了合并输入以获得所需的行为。

据我所知(并且我对其进行了测试),这个 hack 是有效的。但我的问题是我怎样才能以正确的 clojure 方式做到这一点?这个解决方案看起来很复杂

当然,一旦 critical 函数被执行,remove-lock 就必须被调用。

【问题讨论】:

  • 你能在这个例子中添加一个动机/用例吗?由于每个线程都有自己的局部变量副本,并且由于函数输入在 Clojure 中是不可变的,因此似乎每个线程都已完全隔离。如果您希望改变某些共享的全局状态,则不会在您的示例中显示。
  • 对于数据库表中特定标签下的每个附加值,我需要调用服务并更新该值和该标签中的每个值。同时,没有其他进程应该尝试更新标签。这就是为什么我想在我的服务仍在进行计算时阻止所有其他进程访问这个关键功能
  • 这通常不会同时发生,但当它发生时需要被阻止

标签: concurrency clojure


【解决方案1】:

您应该为此使用数据库事务。这是 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 中查询语言 ClojureJavaid 值。然后我们使用列desc 和外键langId 向表releases 添加行。因为两个insert-multi! 语句都是通过(jdbc/with-db-transaction 包装的,所以如果任何其他线程在完成之前更新了数据库,则事务将回滚。

如果我们的事务失败,上面的代码需要一个重试循环来捕获异常,然后重试(可能有随机延迟)。你可以找到the entire sample code here


更新

我的示例是针对诸如 Postgres 之类的 SQL 数据库。对于 Datomic,我相信你会想要一个像 db.fn/cas 这样的函数。 See the Datomic docs 了解详情。您也可以询问 on the Datomic mailing list 或在 StackOverflow 上发布更具体的 Datomic 问题。

对于 Postgres 或 Datomic,仅当您更改的特定行/实体也被另一个线程更改时,事务才会中止。它不会锁定整个数据库。

【讨论】:

  • 太好了,我理解您的解决方案,但是当您说“如果任何其他线程更新数据库”时,这让我觉得是否有必要。我们使用 Datomic 数据库,许多服务可能会更新数据库,如果发生这种情况,我不想回滚 tx,因为那样就没有必要了。如果出现具有相同标签的其他进程,我只关心回滚 tx。这个解决方案和使用上面提到的锁定有什么区别。据我了解,锁定会更好,因为它不会留下回滚选项。我是否正确理解您的方法?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-22
  • 1970-01-01
  • 1970-01-01
  • 2021-10-19
  • 2013-08-22
相关资源
最近更新 更多