在 Clojure 中管理资源池的最佳方式

Best way to manage a pool of resources in Clojure

我有一个 Web 服务端点,它使用来自 Java 库的可变资源。该 Web 服务端点可以同时接收多个查询。 (端点是使用 Ring/Compojure 实现的)。创建这些资源的成本很高,因此为每个 Web 服务调用重新创建它们的效率确实很低。

我想要做的是创建一个 pool 我在 Web 服务启动时填充的资源。然后每次调用端点时,它都会从池中获取资源,将其用于处理,然后将其推回池中并等待下一次调用发生。

我想知道在 Clojure 中最好的方法是什么?有 "pool" Clojure 库可以帮助我吗?

我天真地尝试在原子中使用向量来实现它,其中向量的每个项目都是该资源。然而,它很快了解到它不能真正以这种方式工作。

要实现池,您需要解决 2 个问题:

  1. 并发。使用 locking https://clojuredocs.org/clojure.core/locking 或 ref 而不是原子。请求可以是同时的,所以需要注意两个消费者不可能接收到同一个资源。

  2. 释放资源。考虑使用类似 (with-open ...) 的模式,即扩展为 try-finally 的宏,当您离开块作用域时,资源将释放回开放池。

您可能想要指定 'error' 状态以及 'available' 或 'in use',其中可能需要释放和重新创建资源。

看看这个 kul/pool。它使用 Apache 公共池。希望有用。

这是基于 Timothy Pratley 使用 refs 的想法:

(def pool (ref ['a 'b 'c]))

(defn take' [pool]
  (dosync
    (let [[h & t] @pool]
      (ref-set pool (vec t))
      h)))

(defn put [pool x]
  (dosync
    (alter pool conj x)
    nil))

(take' pool)   ;; => 'a
(put pool 'a)  ;; => nil
(take' pool)   ;; => 'a
(take' pool)   ;; => 'b
(take' pool)   ;; => 'c

也许不是解决这个问题的最佳方式。但我喜欢它的简单。

你也可以使用原子:

(def pool (atom ['c 'b 'a]))

(defn take'
  [pool]
  (loop []
    (let [p @pool]
      (if (compare-and-set! pool p (pop p))
        (peek p)
        (recur)))))

(defn put
  [pool x]
  (swap! pool conj x)
  nil)

(take' pool)   ;; => 'a
(put pool 'a)  ;; => nil
(take' pool)   ;; => 'a
(take' pool)   ;; => 'b
(take' pool)   ;; => 'c