如果我想在服务器端维护一个图像帧队列,我将发送给客户端,我应该使用什么数据结构?
我正在尝试制作一个简单的应用程序,我将在其中将帧发送到服务器,然后服务器将它们推送到其他客户端。
我应该将此队列维护为原子还是参考?
如果我想在服务器端维护一个图像帧队列,我将发送给客户端,我应该使用什么数据结构?
我正在尝试制作一个简单的应用程序,我将在其中将帧发送到服务器,然后服务器将它们推送到其他客户端。
我应该将此队列维护为原子还是参考?
您可以只使用其中一个队列类java.util.concurrent
。轻松访问 Java 标准库毕竟是 Clojure 的强项之一,因此如果 Java 已经提供了可以完成这项工作的东西,那么您不必自己从 Clojure 提供的构建块构建所有内容。
我建议从接口BlockingQueue的实现中选择一些东西。
似乎是core.async的一种可能性。
队列最重要的操作是弹出项目:即从集合中获取和删除项目。
由于这个操作是复合的,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)]
尽管 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))
s呢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
andpop
代替first
and rest
。不要忘记把它放在一个原子中,就像上面的列表一样:)
您还可以使用类似java.util.concurrent.LinkedBlockingDeque的东西。查看这个为 ClojureScript 编译器引入并行构建功能的提交,以获取使用LinkedBlockingDeque
.
您可以尝试agent,想法如下:
对于每个客户端,您都有一个代理,您只需发送命令以将帧传输到客户端。由于代理上的操作是按 FIFO 顺序执行的(至少只要您只有一个发送线程)。
(send-off client transmit-frame frame)