Dr *_*son 0 multithreading locking clojure
我是Clojure的新手,正在编写一个Web应用程序.它包括fn对用户执行的功能user-id,包括读取和写入数据库和文件系统的几个步骤.这些步骤不能由多个线程同时执行(将导致数据库和文件系统不一致),我不相信它们可以使用数据库事务执行.但是,它们特定于一个用户,因此可以针对不同用户同时执行.
因此,如果一个http请求是fn针对特定的,user-id我需要确保它在任何http请求可以fn为此执行之前完成user-id
我想出了一个似乎在REPL中工作但尚未在Web服务器中尝试过的解决方案.但是,由于缺乏Clojure和线程编程,我不确定这是否是解决问题的好方法.以下代码是通过反复试验开发的,并使用了locking函数 - 这似乎违背了Clojure的"无锁"理念.
(ns locking.core)
;;; Check if var representing lock exists in namespace
;;; If not, create it. Creating a new var if one already
;;; exists seems to break the locking.
(defn create-lock-var
[var-name value]
(let [var-sym (symbol var-name)]
(do
(when (nil? (ns-resolve 'locking.core var-sym))
(intern 'locking.core var-sym value))
;; Return lock var
(ns-resolve 'locking.core var-sym))))
;;; Takes an id which represents the lock and the function
;;; which may only run in one thread at a time for a specific id
(defn lock-function
[lock-id transaction]
(let [lock (create-lock-var (str "lock-id-" lock-id) lock-id)]
(future
(locking lock
(transaction)))))
;;; A function to test the locking
(defn test-transaction
[transaction-count sleep]
(dotimes [x transaction-count]
(Thread/sleep sleep)
(println "performing operation" x)))
Run Code Online (Sandbox Code Playgroud)
如果我在REPL中打开三个窗口并执行这些功能,它就可以工作
repl1 > (lock-function 1 #(test-transaction 10 1000)) ; executes immediately
repl2 > (lock-function 1 #(test-transaction 10 1000)) ; waits for repl1 to finish
repl2 > (lock-function 2 #(test-transaction 10 1000)) ; executes immediately because id=2
Run Code Online (Sandbox Code Playgroud)
这可靠吗?有没有更好的方法来解决问题?
UPDATE
正如所指出的,lock变量的创建不是原子的.我重写了这个lock-function功能,似乎工作(不需要create-lock-var)
(def locks (atom {}))
(defn lock-transaction
[lock-id transaction]
(let [lock-key (keyword (str "lock-id-" lock-id))]
(do
(compare-and-set! locks (dissoc @locks lock-key) (assoc @locks lock-key lock-id))
(future
(locking (lock-key @locks)
(transaction))))))
Run Code Online (Sandbox Code Playgroud)
注意:重命名函数lock-transaction,似乎更合适.