2024年的 Clojure 调查中分享你的想法!

欢迎!请查阅关于页面以了解更多有关如何使用本站的信息。

+6
Clojure

我们现在经常遇到这个问题,因为现在我们将一个由 Clojure 组成的系统规模扩大了很多。我想数字只是在跟我们开玩笑,但无论怎样,这都阻碍了 Clojure 在任何多线程环境中的使用。我已经编译了一个最小化 示例 来重现这个问题。所以,问题是:

  • 是否有这个问题的解决办法?(类似于我们可以设置的一些动态变量,我没有找到,可能还有其他方法)
  • 为什么这被认为是一个“重锤”?(例如,要求很少被执行,大多是在单线程环境中(比如repl),而大多数生产环境将从中受益)
  • 有没有什么线索可以解释为什么会这样?(阅读源代码,我不太理解为什么会这样,可能是某些ns要求(因此添加了ns)并添加了所有符号,而在那里时,另一个线程在要求它时替换了那个ns)

1 答案

+2

存在一个私有函数 serialized-require,该函数在执行 require 之前简单地获取一个全局锁,你可以使用它,或者在你的 Java 代码中针对任何使用 clojure.core/require 的地方执行相同操作,即通过在单个对象上的同步方法中调用所有 require 调用来获取全局锁。

感谢您的回答,我知道这一点,然而,在我们这种情况下这是不可行的 `:(`,因为至少有一个这些类是 AOT 编译的,这会使用 `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` 的定义以使其成为锁定的版本。
悄悄地说,require-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生成一个类,并且在多个线程中创建该类的实例,那么生成的类初始化器很可能会在不安全的方式下内部调用实现Clojure命名空间的`require`。我们目前似乎存在这个问题。
...