Multithreading 确保Clojure中只有一个服务实例正在运行/启动/停止的规范方法?
我正在用Clojure编写一个由Neo4j支持的有状态服务器,它可以服务套接字请求,比如HTTP。当然,这意味着我需要能够从这个服务器中启动和停止套接字服务器。在设计方面,我希望能够在这个服务器中声明一个“服务”,并启动和停止它 在Clojure中,我想强调的是如何确保启动和停止这些服务是线程安全的。我正在编写的这个服务器将嵌入NREPL,并以并行方式处理传入的请求。其中一些请求将是管理性的:启动服务X,停止服务Y。这就增加了两个启动请求同时进入的可能性Multithreading 确保Clojure中只有一个服务实例正在运行/启动/停止的规范方法?,multithreading,clojure,parallel-processing,singleton,stm,Multithreading,Clojure,Parallel Processing,Singleton,Stm,我正在用Clojure编写一个由Neo4j支持的有状态服务器,它可以服务套接字请求,比如HTTP。当然,这意味着我需要能够从这个服务器中启动和停止套接字服务器。在设计方面,我希望能够在这个服务器中声明一个“服务”,并启动和停止它 在Clojure中,我想强调的是如何确保启动和停止这些服务是线程安全的。我正在编写的这个服务器将嵌入NREPL,并以并行方式处理传入的请求。其中一些请求将是管理性的:启动服务X,停止服务Y。这就增加了两个启动请求同时进入的可能性 启动应同步检查“运行”标志和“启动”标志
(ns extenium.db
(:require [clojure.tools.logging :as log])
(:import org.neo4j.graphdb.factory.GraphDatabaseFactory))
(def ^:private
db- (ref {:ref nil
:running false
:starting false
:stopping false}))
(defn stop []
(dosync
(if (or (not (:running (ensure db-)))
(:stopping (ensure db-)))
(throw (IllegalStateException. "Database already stopped or stopping."))
(alter db- assoc :stopping true)))
(try
(log/info "Stopping database")
(.shutdown (:ref db-))
(dosync
(alter db- assoc :ref nil))
(log/info "Stopped database")
(finally
(dosync
(alter db- assoc :stopping false)))))
在try块中,我记录,然后调用。关机,然后再次记录。如果第一个日志失败(可能发生I/O异常),则(:stopping db-)将设置为false,这将取消阻止它,并且是正常的。shutdown是Neo4j中的一个void函数,所以我不必计算返回值。如果失败,(:stopping db-)设置为false,也可以。然后我将(:ref db-)设置为nil。如果失败了怎么办?(:stopping db-)设置为false,但(:ref db-)保持挂起状态。那是个洞。与第二次日志调用的情况相同。我错过了什么
(ns extenium.db
(:require [clojure.tools.logging :as log])
(:import org.neo4j.graphdb.factory.GraphDatabaseFactory))
(def ^:private
db- (ref {:ref nil
:running false
:starting false
:stopping false}))
(defn stop []
(dosync
(if (or (not (:running (ensure db-)))
(:stopping (ensure db-)))
(throw (IllegalStateException. "Database already stopped or stopping."))
(alter db- assoc :stopping true)))
(try
(log/info "Stopping database")
(.shutdown (:ref db-))
(dosync
(alter db- assoc :ref nil))
(log/info "Stopped database")
(finally
(dosync
(alter db- assoc :stopping false)))))
如果我只是使用Clojure的锁定原语而不是ref-dance,这会更好吗?这实际上是一个简单锁定的自然选择:
(locking x
(do-stuff))
此处x
是要同步的对象
详细说明:启动和停止服务是一种副作用;不应从事务内部启动副作用,除非可能作为代理操作。这里的锁正是设计所需要的。请注意,在Clojure中使用它们并没有什么错,因为它们非常适合当前的问题,事实上我想说,锁定
是这里的标准解决方案。(参见Stuart Halloway的《Clojure编程》(第1版)中介绍的Clojure库使用锁的示例,该锁在莱宁根得到了广泛的应用。)
更新:添加快速故障行为:
这仍然非常适合锁,即(Javadoc的follow链接):
如果锁获取成功,将执行
(do stuff)
;否则,(做其他事情)
就会发生。当前线程在这两种情况下都不会阻塞。这听起来像是代理的一个很好的用例,它们允许您将更改序列化到一个可变状态,具有良好的概述。
您可以使用错误处理程序和代理错误方法来处理异常,而无需担心锁或争用条件
(def service (agent {:status :stopped}))
(defn start-service [{:keys [status] :as curr}]
(if (= :stopped status)
(do
(println "starting service")
{:status :started})
(do
(println "service already running")
curr)))
;; start the service like this
(send-off service start-service)
;; gets the current status of the service
@service
我在遵循runonce函数的逻辑方面有点困难。为什么把哨兵包裹在一个原子里?另外,我可以在同一个对象上同步start和stop,但是同时调用start或stop会阻塞。我希望他们尽快失败。有这样的模式吗?我已经修改了答案以考虑到这一点。至于Lancet的sentinel,其想法是您只希望包装在
runonce
中的函数在JVM的整个生命周期中运行一次,而不是“在任何给定时刻一次”/“一次只在一个线程中运行一次”,因此(1)您可以通过在sentinel上同步来防止同时执行,(2)通过检查前一次调用是否替换了atom中的值,可以防止在第一次调用之后再调用该函数。为(2)重用sentinel是很自然的,但与(1)中的角色无关。