是否可以在 Clojure 中复制事务死锁?

Is it possible to replicate a transaction deadlock in Clojure?

在SQL中复制事务死锁相对容易。

==SESSION1==
begin tran
update table1 set ... where ...
[hold off further action - begin on next session]

==SESSION2==
begin 
update table1 set ... where ...
[hold off further action - begin on next session]

==SESSION3==
<list blocked transactions - see session2>

现在使用 Clojure 事务 - 您不能只打开它们并保持打开状态,s 表达式不允许您这样做。

所以我对上面的场景很好奇。

我的问题是:是否可以在 Clojure 中复制事务死锁?

根据 Rich Hickey 的说法:

Clojure's STM and agent mechanisms are deadlock-free. They are not message-passing systems with blocking selective receive. The STM uses locks internally, but does lock-conflict detection and resolution automatically.

可以在this组中找到更多详细信息。

Clojure 中的 STM 旨在提供对引用的原子、一致和隔离的操作,没有 锁定。如 refs documentation 中所述,为实现此目的实施了多项功能,但要点之一是拥有一个 "optimistic" 策略,该策略处理每个事务的数据版本并将此版本与 ref 进行比较编写时的版本。

这种乐观策略也可以在数据库中实现,例如在 Oracle.

无论如何,在 Clojure 中,如果你真的想创建一个死锁,你将不得不使用低级机制,例如 locking 宏,它显式地在一个对象上创建一个锁(与 synchronized in Java) 并显式管理对共享资源的访问。

编辑:活锁示例 此示例来自 Clojure Programming、@cgrand 和 al。

(let [retry-count (agent 0)
      x (ref 0)]
  (try
    (dosync  ;; transaction A
      @(future (dosync ;; transaction B
                 (send-off retry-count inc)
                 (ref-set x 1)))
      (ref-set x 2))
    (catch Exception e (println (str "caught exception: " (.getMessage e))))
    (finally
    (await retry-count)))
  [@x @retry-count])

caught exception: Transaction failed after reaching retry limit
[1 10000]
user> 

事务A在repl线程中执行。事务 B 将在单独的线程中执行,但由于 future 在 A 中被取消引用,因此它会阻塞直到 B 完成。当 A 尝试 (ref-set x 2) 时,x 已被 B 修改,这会触发 A 的重试,从而产生新的线程和 B 事务……直到达到最大重试次数并引发异常。