我在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)))))
您可以在解决方案中看到,我在这里使用了键和值,尽管我只锁定键,但是能够使用映射而不是数组是有好处的,因为我使用了仅在map合并时才在映射中合并的merge属性不包含此值,并且由于current-locks
是ref
,因此我使用了alter
并交换了合并输入以获取所需的行为。
据我所知(我已经对其进行测试),此hack有效。但是我的问题是如何以正确的Clojure方法执行此操作?这个解决方案似乎很复杂
当然,一旦执行 critical 函数,就必须调用remove-lock
。
答案 0 :(得分:2)
您应该为此使用数据库事务。这是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
的{{1}}值。然后,我们将列Java
和外键releases
添加到表desc
中。因为两个langId
语句都是通过insert-multi!
包装的,所以如果有任何其他线程在完成之前更新了数据库,则事务将回滚。
如果我们的交易失败,上面的代码将需要一个重试循环来捕获异常,然后重试(可能会有随机延迟)。您可以找到the entire sample code here。
更新
我的示例用于SQL数据库,例如Postgres。对于Datomic,我相信您将需要诸如(jdbc/with-db-transaction
之类的函数。 See the Datomic docs了解更多详细信息。您还可以问on the Datomic mailing list或在StackOverflow上发布更具体的Datomic问题。
对于Postgres或Datomic,仅当您更改的特定行/实体也被另一个线程更改时,事务才会中止。它不会锁定整个数据库。