在clojure中实现多个锁定

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,似乎更合适.

ama*_*loy 5

不要在命名空间中使用N变量,使用包含1个哈希映射的原子将N个符号映射到N个锁.这可以修复您当前的竞争条件,避免创建一堆愚蠢的变量,并且无论如何都更容易编写.