【问题标题】:Synchronising threads for multiple readers / single writer in Clojure在 Clojure 中为多个读取器/单个写入器同步线程
【发布时间】:2011-06-13 16:44:53
【问题描述】:

我有一些只能从多个线程以序列化方式调用的非线程安全代码(共享数据的编写器),但我不想阻止任何其他线程安全的工作(多个读取器)当这段代码没有被调用时。

这本质上是一种多读取器/单写入器类型锁定情况,写入器需要排除读取器和其他写入器。

即我有两个功能:

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

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

以及一些正在运行(可能在循环中)的线程:

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

如果任何单个线程正在执行 writer 函数,则所有其他线程都必须阻塞。即在任何时候:

  • 一个线程正在执行 作家和所有人 其他人被屏蔽了
  • 多线程是 执行阅读器功能,可能有些线程是 阻塞等待执行 完成所有阅读器后的作者

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

【问题讨论】:

    标签: multithreading concurrency clojure


    【解决方案1】:

    看看 java.util.concurrent.locks.ReentrantReadWriteLock。此类允许您拥有多个阅读器,这些阅读器一次不会在一个编写器上相互竞争。

    【讨论】:

    • RRWL 会这样做,但您不应该在 Clojure 中使用它们来解决这个问题。如果您使用 RRWL 来保护您的数据,那么您就没有利用 Clojure 的不可变数据结构、refs 或 STM。 Clojure 对 Clojure 数据结构的引用已经允许多个阅读器。因为它们是不可变的和持久的,所以它们可以在没有阻塞写入器的任何时候被取消引用。阅读clojure.org/refs
    • 谢谢拉尔夫!看起来它符合我的需要。知道是否有与 Clojure 等效的功能,或者我是否需要使用 Java 互操作来获得此功能?
    • @mikera:正如 Alex Miller 在他的评论中所说,refs 的行为是这样的,但据我所知,Clojure 没有特定的功能来做到这一点。
    【解决方案2】:

    将您的数据放入ref。数据应该是 Clojure 数据结构(不是 Java 类)。使用dosync 围绕读写创建事务。

    示例。因为您将编写器拆分为一个单独的函数,所以该函数必须使用alter 之类的东西修改一个引用。这样做需要一个事务 (dosync)。您可以依赖仅在 dosync 中调用 writer,但您也可以将 dosync 放入 write 中并依赖嵌套事务执行您想要的操作 - 这使得 writer 可以安全地调用或调用事务。

    (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

    您没有询问此案例,但请务必注意,任何人都可以随时通过取消引用来读取共享引用。这不会阻止并发写入者。

    【讨论】:

    • @mikera,写作是外部过程的副作用吗?在那种情况下,我认为 refs 不能在这里完成这项工作,它仅用于相同的进程共享状态。我只知道使用代理的部分解决方案。
    • 嗨,亚历克斯,感谢您的回答!作为 100% Clojure 方法看起来很棒,但问题是作者正在执行我无法直接控制的副作用代码(实际上是在 Java 库中)。因此它不能很好地与 Clojure 事务配合使用,我需要以某种方式反映库的锁定要求......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多