Locks can be used to control access to shared data by several processes.
The two main symbols used in locking are the function make-lock, to create a lock, and the macro with-lock, to execute a body of code while holding the specified lock.
A lock has a name (a string) and several other components. The printed representation of a lock shows the name of the lock and whether it is currently locked. Additionally if the lock is locked it shows the name of the process holding the lock, and how many times that process has locked it. For example:
#<MP:LOCK "my-lock" Locked 2 times by "My Process" 2008CAD8>
The function lock-owner returns the process that locked a given lock.
The function lock-name returns the name of a lock.
The function process-lock blocks the current process until a given lock is claimed or a timeout passes, and process-unlock releases the lock.
The macro with-lock executes code with a lock held, and releases the lock on exit, as if by process-lock and process-unlock.
If you need to avoid blocking on a lock that is held by some other thread, then use with-lock with timeout 0, like this:
(unless (mp:with-lock (lock nil 0)
(code-to-run-if-locked)
t)
(code-to-run-if-not-locked))
The macros with-sharing-lock and with-exclusive-lock can be used with sharing locks.
The keyword argument recursivep to make-lock, when true, allows the lock to be locked recursively. recursivep is true by default. If recursivep is false then trying to lock again causes an error. This is useful for debugging code where the lock is not expected to be claimed recursively.
The keyword argument sharing to make-lock, when true, creates an "sharing" lock object, which supports sharing and exclusive locking. A sharing lock is handled by different functions and methods. See with-exclusive-lock, with-sharing-lock, process-exclusive-lock, process-exclusive-unlock, process-sharing-lock and process-sharing-unlock.
See lock-recursive-p, lock-owned-by-current-process-p, lock-owner, lock-locked-p and lock-recursively-locked-p.
In compiled code process-lock, process-exclusive-lock and process-sharing-lock are guaranteed to return if they locked their argument. In other words there will not be any throw between the time they locked the lock and the time they return. That means that in compiled code the next form will at least start executing, and if it is an unwind-protect
the cleanup forms will at least start executing. (If the code is evaluated, this is not guaranteed.) "Locking" here also means incrementing the count of a lock that is already held by the current thread.
However these functions may throw before locking. For example, in the following code process-lock may throw without locking, for example because something interrupts the process by process-interrupt:
(unwind-protect
(progn (mp:process-lock lock)
(whatever))
(mp:process-unlock lock))
If this call to process-lock does throw without locking, then process-unlock will be called on a lock that is not locked.
The correct code that guarantees (when compiled) that process-unlock is called on exit only when process-lock did lock is:
(mp:process-lock lock)
(unwind-protect
(whatever)
(mp:process-unlock lock))
Conversely, process-unlock, process-exclusive-unlock and process-sharing-unlock guarantee to successfully unlock the lock, but are not guaranteed to return.
For example, the following code may fail to call another-cleanup
:
(mp:process-lock lock)
(unwind-protect
(whatever)
(mp:process-unlock lock)
(another-cleanup))
If another-cleanup
is essential to execute in all throws, it needs its own unwind-protect
:
(mp:process-lock lock)
(unwind-protect
(whatever)
(unwind-protect
(mp:process-unlock lock)
(another-cleanup)))
Note: the guarantees described in this section are relevant only in compiled code.
LispWorks User Guide and Reference Manual - 13 Feb 2015