我正在开发Clojure / Jetty网络服务。我有一个特殊的网址,我希望一次只能为一个请求提供服务。如果请求了url,并且在它返回之前,再次请求url,我想立即返回。所以在更多core.clj中,我定义了我的路由,我有类似的东西:
(def work-in-progress (ref false))
然后某个时候
(compojure.core/GET "/myapp/internal/do-work" []
(if @work-in-progress
"Work in Progress please try again later"
(do
(dosync
(ref-set work-in-progress true))
(do-the-work)
(dosync
(ref-set rebuild-in-progress false))
"Job completed Successfully")))
我在本地Jetty服务器上尝试了这个,但我似乎能够两次点击网址并将工作翻倍。在线程Web服务器环境中,在Clojure中实现这一点的好方法/方法是什么?
答案 0 :(得分:4)
想象一下问题中提出的解决方案的后续race condition。
@work-in-progress
为false
,因此输入do
表达式。但是,在设法将work-in-progress
的值设置为true
... @work-in-progress
为false,因此输入do
表达式。现在两个线程同时执行(do-the-work)
。那不是我们想要的。
要防止此问题,请在dosync
事务中检查并设置ref的值。
(compojure.core/GET "/myapp/internal/do-work" []
(if (dosync
(when-not @work-in-progress
(ref-set work-in-progress true)))
(try
(do-the-work)
"Job completed Successfully"
(finally
(dosync
(ref-set work-in-progress false))))
"Work in Progress please try again later"))
在这种情况下,您可能会发现另一个有用的抽象是原子和compare-and-set!
。
(def work-in-progress (atom false))
(compojure.core/GET "/myapp/internal/do-work" []
(if (compare-and-set! work-in-progress false true)
(try
(do-the-work)
"Job completed Successfully"
(finally
(reset! work-in-progress false)))
"Work in Progress please try again later"))
答案 1 :(得分:2)
实际上这是锁的自然用例;特别是java.util.concurrent.locks.ReentrantLock
。
在我对早期SO问题的回答中出现了同样的模式Canonical Way to Ensure Only One Instance of a Service Is Running / Starting / Stopping in Clojure?;我将在这里重复相关的代码:
(import java.util.concurrent.locks.ReentrantLock)
(def lock (ReentrantLock.))
(defn start []
(if (.tryLock lock)
(try
(do-stuff)
(finally (.unlock lock)))
(do-other-stuff)))
tryLock
方法尝试获取锁定,如果成功则返回true
,否则返回false
,在任何一种情况下都不会阻塞。
答案 2 :(得分:1)
考虑排队对资源的访问 - 除了获得与锁/标志相同的功能之外,队列还可以让您观察资源争用,以及其他优点。