在Common Lisp中处理并发文件访问

Ell*_*son 4 concurrency file-io common-lisp race-condition allegro-cl

多个用户需要使用Common Lisp中创建的接口访问同一文件目录.发生这种情况时会出现许多竞争条件 例如,当多个用户添加或删除具有相同时间的文件时.在工作完成时,lisp中是否有办法"锁定"特定目录?这与多线程环境中的"synchronized"块类似,但我有单独的Lisp实例.我在Windows上使用Allegro CL.

编辑:对此问题的不同解决方案的想法也将不胜感激.

sds*_*sds 5

OS级

CLISP提供stream-lockwith-stream-lock哪个接口fcntlLockFileEx.这些将锁定打开的流和文件.

您可以使用FFI在其他CL实现中调用这些OS功能.

目录只是一个(特殊)文件,因此fcntl应该能够锁定它(必须仔细考虑"写入目录"意味着什么).

Windows世界虽然复杂得多.我不认为可以使用库函数锁定目录.

应用级

您可以自己实施协作锁定.这意味着只有使用您的库的应用程序才会尊重锁定,因此您将能够修复应用程序之外的可能问题.

例如(未经测试!):

(defun file-lock (f)
  "return the name of the lock file for this file"
  (concatenate 'sting f "-my-lock-suffix")) ; or use pathname functions...
(defun lock-file-once (f)
  "try to lock file once"
  (open (file-lock f) :direction :probe :if-exists nil))
(defun lock-file (f)
  "block until the file is locked"
  (loop :until (lock-file-once f)
    :do (sleep 1)))
(defun unlock-file (f)
  "remove the lock"
  (delete-file (file-lock f)))
(defmacro with-lock-file (f &body body)
  "lock the file, run body, unlock it"
  (let ((fn (gensym "with-lock-file-f")))
    `(let ((,fn ,f))
       (unwind-protect
            (progn (lock-file ,fn)
                   ,@body)
         (unlock-file ,fn)))))
Run Code Online (Sandbox Code Playgroud)

锁定整个目录需要非常简单的技巧来避免死锁:锁定目录意味着锁定其所有后代,因此获取文件锁定需要首先锁定该文件上方的每一个,然后锁定文件,然后解锁上面的每一个.这让我们开始了竞争.

简单的解决方案是拥有一个锁定操作所需的主锁:

(defvar *master-lock* (pathname .....))
(defun lock-file-or-directory-once (path)
  "lock file or directory or fail"
  (with-lock-file *master-lock*
    scan everything below and also above(!) path
    return nil if any relevant locks are found,
    i.e., if anything below path is locked
    or any directory above path is locked))
(defun lock-file-or-directory (path)
  "block until success"
  (loop :until (lock-file-or-directory path)
    :do (sleep 1)))
Run Code Online (Sandbox Code Playgroud)