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

欢迎!请参阅 关于 页面以了解更多关于这个功能的信息。

0
Clojure
我们使用 Clojure 来实现一个“规则引擎”。每个函数代表一个规则,元数据描述了规则并提供一些静态配置。

如果有两个或更多线程同时调用相同的 Var,由于 AReference#meta() 是同步的(参见附图中的红色点),它们会相互阻塞。


(defn
  ^{:rule {:remote-address "127.0.0.1"}}
  example
  [request]
  (let [rule (:rule (meta #'example))]
    (= (:remote-address rule) (:remote-address request))))


*方法:* 用 rwlock 替换同步块,以实现更高的读取并发性。这种方法消除了元数据读取的竞争(请参见注释中的实际案例)。然而,它带来了以下缺点:

* 每个 AReference 都有一个额外的字段(所有命名空间、vars、atoms、refs 和 agents)
* 将锁的构建添加到 AReference 的构建中(影响性能和启动时间)

* 修复: clj-1888-2.patch 用 rwlock 替换了 synchronized,以便实现更高的读取并发性

* 其他方案:*

* 对于 _meta 使用 volatile,对于 alter/reset 使用 synchronized。允许在 volatile 下读取 _meta - 这是否足够安全?
* 从 ReentrantReadWriteLock 继承 AReference,而不是持有它 - 这很奇怪,但可能对内存/构建带来不同的(可能是更好的)影响。

4 答案

0

评论者:alexmiller

在 alterMeta 中,volatile 不足以原子性地读取/更新/写入。

然而,您可以使用 ReadWriteLock 来替换 synchronized。我已经附上一个补丁,如果您有可重复的情况,我很想看看它对您在分析器中看到的状况有何影响。

这将带来一些潜在的问题需要考虑 - 这将增加每个引用的内存(锁实例)并减缓构建(锁的构建)速度,以实现更多的并发读取的好处。

0
评论由:rkapsi_ 发布

嗨 Alex,

我确实有一个可重现的案例。在该补丁应用后,阻塞确实消失了(见附图)。这些 "WorkerThreads" 上的剩余阻塞代码是 {{sun.nio.ch.SelectorImpl.select(long)}}(即与 clojure 无关)。

您可以通过在无限循环中同时执行以下类似的代码来自行重新创建此问题。


(defn
  ^{:rule {:remote-address "127.0.0.1"}}
  example
  [request]
  (let [rule (:rule (meta #'example))]
    (= (:remote-address rule) (:remote-address request))))


关于补丁的建议:将元锁设为 {{final}} 字段,并可能将读写锁拉入局部变量以避免双重方法调用。


alterMeta(...)
  Lock w = _metaLock.writeLock();
  w.lock();
  try {
    // ...
  } finally {
    w.unlock();
  }
}
0

评论者:alexmiller

标记为预筛选的

0
参考: https://clojure.atlassian.net/browse/CLJ-1888 (由rkapsi报告)
...