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 替换 synchronized 块以实现更高的读并发性。这种方法消除了元数据读的竞争(见注释中的实际例子)。然而,也存在以下缺点:

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

* 补丁:clj-1888-2.patch 将 synchronized 替换为 rwlock 以实现更高的读并发性

* 其他方案:*

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

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))))


对补丁的建议:将元锁(meta lock)设置为 {{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社区成员提问并获得答案。
...