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