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(所有命名空间、变量、原子、引用和代理)增加一个额外字段
* 将锁的构建纳入到 AReference 的构建中(影响性能和启动时间)

* 补丁:clj-1888-2.patch 将同步替换为 rwlock 以提供更高的读取并发性

* 替代方案:*

* 对于 _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报告)
欢迎使用Clojure Q&A,您可以在Clojure社区成员这里提问并获得答案。
...