Work queues in Clojure
我正在使用Clojure应用程序从Web API访问数据。我将要提出很多请求,许多请求将导致提出更多请求,因此,我希望将请求URL保留在队列中,这样在每次下载之间将间隔60秒。
在这篇博客文章之后,我将其整合在一起:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | (def queue-delay (* 1000 60)) ; one minute (defn offer! [q x] (.offerLast q x) q) (defn take! [q] (.takeFirst q)) (def my-queue (java.util.concurrent.LinkedBlockingDeque.)) (defn- process-queue-item [item] (println">>" item) ; this would be replaced by downloading `item` (Thread/sleep queue-delay)) |
如果我在代码中的某个位置包含
同样,与通常Clojure对并发的热爱相反,我想确保一次仅发出一个请求,并且我的程序等待60秒发出每个后续请求。
我认为这个"堆栈溢出"问题是相关的,但是我不确定如何使它适应我的要求。如何连续轮询队列并确保一次仅运行一个请求?
这是我做的一个有趣的项目的代码片段。它不是完美的,但是可以让您了解我如何解决"等待第一项55秒"的问题。它基本上遍历承诺,使用期货立即处理事情或直到"变为"可用的承诺为止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | (defn ^:private process [queues] (loop [[q & qs :as q+qs] queues p (atom true)] (when-not (Thread/interrupted) (if (or (< (count (:promises @work-manager)) (:max-workers @work-manager)) @p) ; blocks until a worker is available (if-let [job (dequeue q)] (let [f (future-call #(process-job job))] (recur queues (request-promise-from-work-manager))) (do (Thread/sleep 5000) (recur (if (nil? qs) queues qs) p))) (recur q+qs (request-promise-from-work-manager)))))) |
也许您可以做类似的事情?代码不是很好,可能需要重写以使用
我最终滚动了自己的小型图书馆,称之为"简单队列"。您可以在GitHub上阅读完整的文档,但是这里是完整的源代码。我不会更新此答案,因此,如果您想使用此库,请从GitHub获取源代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | (ns com.github.bdesham.simple-queue) (defn new-queue "Creates a new queue. Each trigger from the timer will cause the function f to be invoked with the next item from the queue. The queue begins processing immediately, which in practice means that the first item to be added to the queue is processed immediately." [f & opts] (let [options (into {:delaytime 1} (select-keys (apply hash-map opts) [:delaytime])), delaytime (:delaytime options), queue {:queue (java.util.concurrent.LinkedBlockingDeque.)}, task (proxy [java.util.TimerTask] [] (run [] (let [item (.takeFirst (:queue queue)), value (:value item), prom (:promise item)] (if prom (deliver prom (f value)) (f value))))), timer (java.util.Timer.)] (.schedule timer task 0 (int (* 1000 delaytime))) (assoc queue :timer timer))) (defn cancel "Permanently stops execution of the queue. If a task is already executing then it proceeds unharmed." [queue] (.cancel (:timer queue))) (defn process "Adds an item to the queue, blocking until it has been processed. Returns (f item)." [queue item] (let [prom (promise)] (.offerLast (:queue queue) {:value item, :promise prom}) @prom)) (defn add "Adds an item to the queue and returns immediately. The value of (f item) is discarded, so presumably f has side effects if you're using this." [queue item] (.offerLast (:queue queue) {:value item, :promise nil})) |
使用此队列返回值的示例:
1 2 3 | (def url-queue (q/new-queue slurp :delaytime 30)) (def github (q/process url-queue"https://github.com")) (def google (q/process url-queue"http://www.google.com")) |
对
纯粹出于副作用使用此队列的示例:
1 2 3 4 5 6 7 8 9 10 | (defn cache-url [{url :url, filename :filename}] (spit (java.io.File. filename) (slurp url))) (def url-queue (q/new-queue cache-url :delaytime 30)) (q/add url-queue {:url"https://github.com", :filename"github.html"}) ; returns immediately (q/add url-queue {:url"https://google.com", :filename"google.html"}) ; returns immediately |
现在,对
这很可能是疯狂的,但是您总是可以使用这样的函数来创建慢下来的延迟序列:
1 2 3 4 5 6 7 8 | (defn slow-seq [delay-ms coll] "Creates a lazy sequence with delays between each element" (lazy-seq (if-let [s (seq coll)] (do (Thread/sleep delay-ms) (cons (first s) (slow-seq delay-ms (rest s))))))) |
基本上,这将确保每个函数调用之间的延迟。
您可以将其与以下内容一起使用,以毫秒为单位:
1 2 | (doseq [i (slow-seq 500 (range 10))] (println (rand-int 10)) |
或者,您也可以将函数调用放入序列中,例如:
1 | (take 10 (slow-seq 500 (repeatedly #(rand-int 10)))) |
显然,在以上两种情况下,您都可以用用于执行/触发下载的任何代码替换