操作系统级别
CLISP 提供与fcntl 或LockFileEx 接口的stream-lock 和with-stream-lock。这些将锁定打开的流和文件。
您可以使用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)))))
锁定整个目录需要不平凡的技巧以避免死锁:锁定目录意味着锁定其所有后代,因此获取文件锁定需要首先锁定该文件上方的所有内容,然后锁定文件,然后解锁上面的所有内容。
这为我们打开了一个竞争条件。
简单的解决方案是拥有任何锁定操作都需要的主锁:
(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)))