2024 Clojure 状态调查!中分享您的想法。

欢迎!请参阅关于页面以了解更多关于如何工作的信息。

+6
Clojure

我们现在看到这个问题很常见,因为我们大量使用了Clojure开发了一个系统。我想数字只是与我们作对,但不管怎样,这妨碍了Clojure在多线程环境中的使用。我已经创建了一个最小化示例来复制这个问题。所以问题来了:

  • 有没有解决这个问题的方法?(类似于某些我们设置的动态变量,我没有找到,但可能存在。)
  • 为什么这被认为是“重锤”?(即很少使用,而且主要是在单线程环境(例如repl)中,而在大多数生产环境中,这将受益。)
  • 有什么线索说明为什么会发生这种情况?(阅读源代码,我不太理解为什么会发生这种情况,可能是某些ns要求(因此添加了ns),添加了所有符号,期间另一个线程在要求时替换了该ns。)

1 答案

+2

有一个私有函数serialized-require,它在进行require之前简单地获取一个全局锁,你可以使用这个函数,或者在你的Java代码中对任何clojure.core/require的使用执行相同的事情,例如,通过在单个对象上的synchronized方法内调用所有的require调用来实现。

感谢您的回答,我知道这一点,然而,在我们这个案例中这是不可行的 `:(` 因为至少有一个这些类是 AOT 编译的,这反过来又使用了 `loadWithClass`,而 `loadWithClass` 又反过来调用 `require` (来加载 `clj` 文件),而我们无法控制这一点。
如果您无法更改 AOT 编译的代码,因为这确实不在您的控制之下,那么您是否可以评估一下以下代码,并确保在系统启动时从多个线程调用 `require` 之前进行评估?当然,println 语句是可选的。


(def original-require clojure.core/require)

(defn my-serialized-require [& args]
  (locking clojure.lang.RT/REQUIRE_LOCK
    (println "my-serialized-require 获取了锁...")
    (apply original-require args)
    (println "my-serialized-require 释放锁...")))

(alter-var-root #'clojure.core/require (fn [& args] my-serialized-require))


如果以上方案对您不可行,您是否允许编译 Clojure 源代码的修改版本并在项目中使用它?如果可以,您可以将 `require` 的定义修改为带锁的版本。
就我所知,`requiring-resolve` 是线程安全(加锁)`require` 的公共 API,我的理解是计划在某个时刻(可能是 Clojure 1.11?Alex 可能对此有独到见解)将普通的 `require` 通过此机制实现线程安全,但在将此类更改应用于 Clojure 的基本部分之前,还需要进行一些分析和研究。
再次感谢,现在我明白了问题所在,我认为我们可以为我们的情况找到一个解决方案,但是,这并没有解决根本问题,我也想看到一个长期的普遍解决方案。
再次感谢,现在我明白了问题所在,我认为我们可以为我们的情况找到一个解决方案,但是,这并没有解决根本问题,我也想看到一个长期的普遍解决方案。
我理解您希望有一个长期普遍的解决方案。我与您的问答部分中提到的临时解决方案很感兴趣。我无法控制Clojure发布版本的内容。官方Clojure维护者也会阅读这些问题,并且他们会以他们认为的长期普遍解决方案回应。
非常感谢您的考虑!
大家好,我在重复上面的问题 - 是的,这是一个已知问题,已经列入Clojure 1.11的候选列表中。并行(冲突)加载相对较少。`requiring-resolve`是在1.10中添加的,用于覆盖最常见的动态加载使用场景(可能轻易发生竞态条件)。

正如Andy提到的,clojure.lang.RT/REQUIRE_LOCK有意识地被保留,以便需要用户程序可以参与此锁。这应该仍然被视为实现问题,并且可能会在未来被删除,但与此同时,在您提到的此类情况下,可以使用建议的解决方案。

期望的长期解决方案是使正常的`require`更安全,但这对意味着需要完成大量的分析和测试,目前还未完成。
gen-class可能也是另一个常见用例。如果您从Clojure生成一个类,并在Java内部多线程中创建该类的实例,则生成的类初始化器可能会以非线程安全的方式在幕后`require`实现Clojure命名空间。我们似乎目前存在这个问题。
...