我有一个使用compojure的环形应用程序。 我使用 PersistentQueue 创建了一个原子来存储进程执行的 ID,并阻止重复执行,并使用相同的 ID 向我的 API 发出其他请求。
但是在我的测试中,Atom 运行良好,但仅在我的 API 的同一端点上。 如果我调用其他端点,则行为会有所不同。
我的原子:
(def queue (atom clojure.lang.PersistentQueue/EMPTY))
(defn add-to-queue [number]
(swap! queue conj number))
(defn remove-from-queue [number]
(swap! queue (fn [q] (remove #{number} q))))
(defn queue-has-id? [number]
(let [pr-queue (seq @queue)]
(if-not (nil? pr-queue)
(> (.indexOf pr-queue number) -1)
false)))
举例来说,当我调用端点 http://localhost:3000/run 时,将调用函数添加到队列,并且我的 Atom 内容将交换到具有一个 id 的队列。
原子状态:
value behaviour
[] INIT
[1] CALL RUN WITH ID 1
在我的进程执行过程中,如果我再次调用端点"RUN",我调用函数 queue-has-id? 如果存在 id,则调用函数 queue-has-id? 来阻止,在这种情况下,ID "1"存在,然后执行被阻止。
但是如果我调用其他端点"检索",我的原子队列值是 [1],但 indexOf id 返回 false。
有人知道这个实现有什么问题吗?我所知道的是,在我的应用程序生命周期中,原子被共享到并发进程,为什么会出现这个问题?
首先,您不要以惯用的方式使用队列。队列是一种抽象数据类型,它为具有后续操作的项目提供有序容器:
- 排队,
conj
clojure.lang.PersistentQueue
- 取消排队,
peek
获取头项和pop
返回没有头项的队列 - 可选、空度检查、
empty?
如我所见,您需要一个提供存储唯一编号 (ID) 并在需要时删除它们的集合。我可以建议使用set
数据结构。在这种情况下,您的代码应如下所示
(def numbers (atom #{}))
(defn add-number! [n]
(swap! numbers conj n))
(defn contains-number? [n]
(contains? @numbers n))
(defn remove-number! [n]
(swap! numbers disj n))
但是,如果您出于某种原因仍然想使用PersistentQueue
您的代码应该看起来像
(def queue (ref clojure.lang.PersistentQueue/EMPTY))
(defn enqueue!
"It doesn't check uniqueness."
[item]
(dosync (alter queue conj item)))
(defn has-item?
"Be careful, it is O(n) operation."
[item]
(some #(= item %) @queue))
(defn dequeue!
"Pop element off the queue and returns it"
[]
(dosync
(let [v (peek @queue)]
(alter queue pop)
v)))
(defn remove!
"Because you want to remove all repetition of an item you should
convert current a queue to a sequence, remove items and convert
it back to a queue. It creates intermediate collection and it is
WRONG way to use queue. But it is still achievable."
[item]
(dosync
(alter queue #(apply conj
clojure.lang.PersistentQueue/EMPTY
(remove #{item} %)))))
如您所见,我使用ref
而不是atom
,因为PersistentQueue
类型的性质提供了两个函数peek
和pop
来取消项目队列。dosync
宏可确保同步应用传递给它的所有表达式。这样它就可以确保两个线程不会偷看同一个项目并弹出两次。