如果我想维护服务器端的图像帧队列,我将发送给客户端我应该使用哪种数据结构?
我正在尝试创建一个简单的应用程序,我将向服务器发送帧,然后服务器将它们推送到其他客户端。
我应该将此队列维护为原子还是ref?
答案 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的数据结构和原子来完成此任务。关键是使用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)
构造它-然后可以在其上调用peek
和pop
而不是first
和rest
。别忘了像上面的列表一样将它放在一个原子中:)
您还可以使用类似java.util.concurrent.LinkedBlockingDeque的名称。查看this commit,它为ClojureScript编译器引入了并行构建功能,以使用LinkedBlockingDeque
为例。