请在 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 以实现更高的读并发性

* 替代方案:*

* 对于 _meta 使用 volatile,对于 alter/reset 使用 synchronized。允许在 volatile 的下一次读取 _meta,这是否足够安全?
* 从 ReentrantReadWriteLock 扩展出 AReference,而不是持有它——这很奇怪,但可能会有不同的、潜在的更好的内存/构建占用。

4 个答案

0

评论者:alexmiller

在 alterMeta 中volatile不足,因为您需要以原子方式读取/更新/写入。

但是,您可以使用 ReadWriteLock 代替 synchronized。我已经附上一个补丁来实现这一点——如果您有一个可复现的情况,我将很乐意看看它对您的性能影响。

这将增加每个引用的内存使用(锁的实例),并减慢构建速度(锁的构建)以获得更高的读并发性。

0
评论由:rkapsi_

嗨阿列克斯,

我确实有一个可复现的案例。在应用你的补丁后,阻塞问题确实消失了(见所附图片)。这些“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问答社区,您可以在此提出问题并获得Clojure社区成员的回答。
...