Clojure atom PersistentQueue in a Ring Application behaviour



我有一个使用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。

有人知道这个实现有什么问题吗?我所知道的是,在我的应用程序生命周期中,原子被共享到并发进程,为什么会出现这个问题?

首先,您不要以惯用的方式使用队列。队列是一种抽象数据类型,它为具有后续操作的项目提供有序容器:

  • 排队,conjclojure.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类型的性质提供了两个函数peekpop来取消项目队列。dosync宏可确保同步应用传递给它的所有表达式。这样它就可以确保两个线程不会偷看同一个项目并弹出两次。

最新更新