哪个是理想的Clojure并发构造,用于维护要处理的数据队列?

时间:2013-09-13 12:03:02

标签: clojure

如果我想维护服务器端的图像帧队列,我将发送给客户端我应该使用哪种数据结构?

我正在尝试创建一个简单的应用程序,我将向服务器发送帧,然后服务器将它们推送到其他客户端。

我应该将此队列维护为原子还是ref?

5 个答案:

答案 0 :(得分:5)

您可以使用java.util.concurrent中的一个队列类。轻松访问Java标准库是Clojure的所有优点之一,因此如果Java已经提供了可以完成工作的东西,那么您不必自己从Clojure提供的构建块构建所有内容。

我建议从界面BlockingQueue的实现中选择一些东西。

答案 1 :(得分:2)

队列中最重要的操作是弹出项目:即从集合中获取和删除项目。

由于此操作是复合的,因此refs更自然地适合以原子方式执行,这是 - 防止竞争条件(例如,两个线程获得相同的项目)。

(defn remove-and-get [queue]
  (dosync
   (let [i (peek @queue)]
     (alter queue pop)
     i)))

(def q (ref (clojure.lang.PersistentQueue/EMPTY)))

(dosync
 (commute q conj 42)
 (commute q conj :foo)
 (commute q conj []))

[(seq @q) (remove-and-get q) (seq @q)]
;; evals to [(42 :foo []) 42 (:foo [])]

等效功能也可以用原子实现。

(defn remove-and-get [queue]
  (let [snapshot @queue
        i (peek snapshot)]
    (if (compare-and-set! queue snapshot (pop snapshot))
      i
      (recur queue))))

(def q (atom (clojure.lang.PersistentQueue/EMPTY)))

(swap! q conj 42)
(swap! q conj :foo)
(swap! q conj [])

[(seq @q) (remove-and-get q) (seq @q)]

答案 2 :(得分:2)

似乎是core.async的可能性。

答案 3 :(得分:0)

您可以尝试agents,其想法如下:

对于每个客户端,您有一个代理,您只需发送命令将帧传输到客户端。因为代理上的操作是按FIFO顺序执行的(至少只要你有一个发送线程)。

 (send-off client transmit-frame frame)

答案 4 :(得分:0)

危险!什么不起作用

即使Clojure为您提供了出色的数据结构和原语,也无法使您免于编写竞争条件。查看以下示例,这些示例将不起作用:

;; WILL NOT WORK    

(def queue (atom '(:foo :bar :baz :qux)))

;; the code below is called from threads

;; take the first value of the "queue", derefing it

(let [peeked-val (first @queue)]
  (do-something-with peeked-val)
  ;; update the atom to remove the first value
  ;; DANGER: You've derefed the value above but you're swapping it in a separate step
  ;; (here). Other threads may have read the same value or may have mutated it
  ;; in the meantime!
  (swap! queue rest))

ref个呢?

;; WILL NOT WORK    

(def queue (ref '(:foo :bar :baz :qux)))

;; the code below is called from threads

;; take the first value of the "queue", derefing it, this time in a transaction!

(dosync
  (let [peeked-val (first @queue)]
    (do-something-with peeked-val)
    ;; update the ref to remove the first value in the transaction
    ;; DANGER: Refs give you transactional consistency (i.e. consistency
    ;; between read/write access to other refs) but this doesn't really apply
    ;; here as we only have on ref. Other threads may have read the same value
    ;; or may have mutated it in the meantime!
    (alter queue rest)))

在Clojure中解决

您可以使用Clojure的数据结构和原子来完成此任务。关键是使用swap-vals!,因此您只需触摸一次原子-否则您将遇到竞争状况,因为如上例所示,您有两项操作:取消引用原子(获取其值)并交换它(更改其值)。

(def queue (atom '(:foo :bar :baz :qux)))

;; use `swap-vals!` on atoms to get both the old and new values of the atom.
;; perfect if you want to peek (get the first value) while doing a pop (removing
;; the first value from the atom, thereby mutating it)

;; use the code below in threads (or somewhere in core.async)
;; it's somewhat non-idiomatic to use `rest` and `first`, see the text below for
;; other options

(let [[old new] (swap-vals! queue rest)]
  (println "here's the popped value:" (first old)))

您也可以改用PersistentQueue,用(clojure.lang.PersistentQueue/EMPTY)构造它-然后可以在其上调用peekpop而不是firstrest 。别忘了像上面的列表一样将它放在一个原子中:)

使用Java数据结构

您还可以使用类似java.util.concurrent.LinkedBlockingDeque的名称。查看this commit,它为ClojureScript编译器引入了并行构建功能,以使用LinkedBlockingDeque为例。