Clojure中针对多个读者/单个写者的线程同步

3

我有一些非线程安全的代码(用于写入共享数据),只能以串行方式从多个线程调用,但当不调用此代码时,我不想阻塞任何其他线程安全工作(多个读者)。

这实质上是一种多读单写类型的锁定情况,其中作者需要排除读者和其他作者。

即我有两个函数:

(defn reader-function [] ....) // only reads from shared data

(defn writer-function [] ....) // writes to shared data

有一些正在运行的线程(可能在循环中),执行以下操作:

(do 
  (reader-function)
  ...
  (writer-function))

如果任何一个线程正在执行写入函数,那么所有其他线程都必须阻塞。也就是说,在任何时候:

  • 一个线程正在执行写入,而所有其他线程都处于阻塞状态
  • 多个线程正在执行读取函数,可能有一些线程正在等待执行写入函数,直到所有读取操作完成后再执行

在Clojure中实现这种同步的最佳方法是什么?

2个回答

3
将数据放入一个ref中。该数据应为Clojure数据结构(而不是Java类)。使用dosync在读写周围创建事务。
例如,由于您将编写器拆分为单独的函数,因此该函数必须使用类似alter的东西修改ref。这样做需要一个事务(dosync)。您可以依赖编写器仅在dosync中调用,但也可以在写入操作中放置dosync并依赖于嵌套事务执行您想要的操作-这使编写器安全地在事务内外调用。
(defn reader [shared] 
  (println "I see" @shared))

(defn writer [shared item]
  (dosync 
    (println "Writing to shared")
    (alter shared conj item)))

;; combine the read and the write in a transaction
(defn combine [shared item]
  (dosync 
    (reader shared)
    (writer shared item)))

;; run a loop that adds n thread-specific items to the ref
(defn test-loop [shared n]
  (doseq [i (range n)]
    (combine shared (str (System/identityHashCode (Thread/currentThread)) "-" i))
    (Thread/sleep 50)))

;; run t threads adding n items in parallel
(defn test-threaded [t n]
  (let [shared (ref [])]
    (doseq [_ (range t)]
      (future (test-loop shared n)))))

使用类似于(test-threaded 3 10)的方式运行测试。

更多信息请参见:http://clojure.org/refs

虽然您没有提及这种情况,但重要的是要注意任何人都可以随时通过解引用共享 ref 来读取它。这并不会阻塞并发写入者。


@mikera,写入是外部进程的副作用吗?如果是这种情况,我认为refs不能在这里发挥作用,因为它仅用于同一进程共享状态。我只知道使用代理来使用部分解决方案。 - bmillare
嗨Alex,感谢你的回答!作为一种100%的Clojure方法看起来很棒,但问题在于作者正在执行具有副作用的代码(实际上在Java库中),我无法直接控制。因此,它与Clojure事务不兼容,我需要以某种方式反映库的锁定要求... - mikera

0

看一下java.util.concurrent.locks.ReentrantReadWriteLock。这个类允许您拥有多个读取器,它们不会同时争用一个写入器。


RRWL可以做到这一点,但您不应该在Clojure中使用它们来解决此问题。 如果您使用RRWL来保护数据,则无法利用Clojure的不可变数据结构、引用或STM。 Clojure数据结构的引用已经允许多个读取器。 由于它们是不可变和持久的,因此可以随时解除引用而无需阻止编写器。 请阅读http://clojure.org/refs。 - Alex Miller
谢谢 Ralph!看起来这正是我需要的。你知道是否有Clojure等效版本,还是我需要使用Java互操作来获取此功能? - mikera
@mikera:正如Alex Miller在他的评论中所说,refs的行为就是这样,但据我所知,Clojure没有特定的函数来实现这一点。 - Ralph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接