Clojure atom PersistentQueue 在环形应用程序中的行为
Clojure atom PersistentQueue in a Ring Application behaviour
我有一个使用 compojure 的戒指应用程序。
我用 PersistentQueue 创建了一个原子来存储进程执行的 ID,并阻止重复执行其他请求到我的 API 具有相同的 ID。
但在我的测试中,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 时,函数 add-to-queue 被调用并且我的 Atom 内容被交换到具有一个 ID 的队列。
Atom 状态:
value behaviour
[] INIT
[1] CALL RUN WITH ID 1
在我的流程执行期间,如果我再次调用端点 'RUN',我会调用函数 queue-has-id?如果 id 存在则阻止,在这种情况下,id '1' 存在则执行被阻止。
但是如果我调用了其他 ENDPOINT 'retrieve',我的原子队列值为 [1] 但 indexOf id 返回 false。
有人知道这个实现有什么问题吗?我所知道的是在我的应用程序生命周期中原子被共享给并发进程,为什么会出现这个问题?
首先,您没有以惯用的方式使用队列。队列是一种抽象数据类型,它为具有下一个操作的项目提供有序容器:
- 排队,
conj
clojure.lang.PersistentQueue
- 出队,
peek
获取头项,pop
到 return 队列,没有头项
- 可选,空值检查,
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
宏确保所有传递给它的表达式都将被同步应用。这样可以确保两个线程不会偷看同一个项目并弹出两次。
我有一个使用 compojure 的戒指应用程序。 我用 PersistentQueue 创建了一个原子来存储进程执行的 ID,并阻止重复执行其他请求到我的 API 具有相同的 ID。
但在我的测试中,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 时,函数 add-to-queue 被调用并且我的 Atom 内容被交换到具有一个 ID 的队列。
Atom 状态:
value behaviour
[] INIT
[1] CALL RUN WITH ID 1
在我的流程执行期间,如果我再次调用端点 'RUN',我会调用函数 queue-has-id?如果 id 存在则阻止,在这种情况下,id '1' 存在则执行被阻止。
但是如果我调用了其他 ENDPOINT 'retrieve',我的原子队列值为 [1] 但 indexOf id 返回 false。
有人知道这个实现有什么问题吗?我所知道的是在我的应用程序生命周期中原子被共享给并发进程,为什么会出现这个问题?
首先,您没有以惯用的方式使用队列。队列是一种抽象数据类型,它为具有下一个操作的项目提供有序容器:
- 排队,
conj
clojure.lang.PersistentQueue
- 出队,
peek
获取头项,pop
到 return 队列,没有头项 - 可选,空值检查,
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
宏确保所有传递给它的表达式都将被同步应用。这样可以确保两个线程不会偷看同一个项目并弹出两次。