All Manuals > LispWorks User Guide and Reference Manual > 19 Multiprocessing

NextPrevUpTopContentsIndex

19.8 Killing a process, interrupts and blocking interrupts

19.8.1 Killing a process

When the function of the process (third argument to process-run-function) returns the process exits, but in many cases it is more convenient to terminate the process without returning all the way to the process function.

The function current-process-kill can be used to kill the current process. It executes all the unwind forms on the stack first. Checking in appropriate places and calling current-process-kill is a convenient and safe way (as long as there are unwind-protect forms where needed) of causing processes to exit when they should.

process-terminate can be used to kill any process. If there is no Terminate Method (see current-process-set-terminate-method) it uses the process interrupting mechanism, so if the other process blocks interrupts it will continue to run until it stops blocking. Because the killing interrupt can happen inside unwind forms of unwind-protect (unless they are executed with interrupts blocked) process-terminate is not safe unless all essential unwinding forms are executed with interrupts blocked. In most cases it is probably easier to not use process-terminate in actual applications.

19.8.2 Interrupting a process

process-interrupt and process-interrupt-list can be used to interrupt a process and execute arbitrary code. Since the interrupt happens at a "random" time, it should have minimal interaction with any data structures that are being modified. For robust applications it is probably better never to use it except during development.

19.8.3 Blocking interrupts

The purpose of blocking interrupts is to prevent a process aborting in the middle of an operation that needs to be completed. A typical example is the cleanup forms of an unwind-protect.

Blocking interrupts does not provide atomicity. Other processes may continue to execute.

Blocking interrupts limits the control that LispWorks has over the processes, so interrupts should not be blocked except when necessary. However, apart from blocking interrupts in a process it does not affect the behavior of the system.

The following macros and functions allow control over blocking interrupts: allowing-block-interrupts, with-interrupts-blocked, current-process-unblock-interrupts and current-process-block-interrupts.

Additionally the macros unwind-protect-blocking-interrupts and unwind-protect-blocking-interrupts-in-cleanups allow your program to prevent interrupts from stopping cleanup forms from completing.

Compatibility note: In LispWorks 5.1 and previous versions, mp:without-preemption and mp:without-interrupts are sometimes used to block interrupts, but they also provide atomicity. In many cases (probably most), they are used to provide atomicity, and in these cases they cannot be replaced by blocking interrupts. To get atomicity in LispWorks 6.0 and later you need to use locks or atomic operations. To get atomicity while debugging, you can also use with-other-threads-disabled.

19.8.4 Old interrupt blocking APIs removed

The macros mp:without-interrupts and mp:without-preemption, which were available in LispWorks 5.1 and earlier, are no longer supported. The semantics of these macros allowed them to be used for several different purposes, which now require specific solutions.

The following subsections show examples of typical uses of the old interrupt blocking APIs together with their replacements. The examples use mp:without-interrupts but the ideas also apply to uses of mp:without-preemption.

19.8.4.1 Atomic increment

Old:

(without-interrupts
 (incf *global-counter*))

New: use low level atomic operations.

(sys:atomic-incf *global-counter*)
19.8.4.2 Atomic push/pop

Old:

(without-interrupts
 (push value *global-list*))
     (without-interrupts
      (pop *global-list*))

New: use low level atomic operations.

(sys:atomic-push value *global-list*)
 
(sys:atomic-pop *global-list*)
19.8.4.3 Atomic push/delete

Old:

(without-interrupts
 (push value *global-list*))
     (without-interrupts
      (setq *global-list* (delete value *global-list*)))

New: use a lock, because delete cannot be done atomically since it reads more than one object before modifying one of them.

(defvar *global-list-lock* (mp:make-lock :name "Global List"))
 
(mp:with-lock (*global-list-lock*)
	      (push value *global-list*))
 
(mp:with-lock (*global-list-lock*)
	      (setq *global-list* (delete value *global-list*)))
19.8.4.4 Atomic plist update

Old:

(without-interrupts
 (setf (getf *global-plist* key) value))
     (without-interrupts
      (getf *global-plist* key))

New: use a lock, because a plist consists of more than one object so cannot be updated with low level atomic operations.

(defvar *global-plist-lock* (mp:make-lock :name "Global Plist"))
 
(mp:with-lock (*global-plist-lock*)
	      (setf (getf *global-plist* key) value))
 
(mp:with-lock (*global-plist-lock*)
	      (getf *global-plist* key))
19.8.4.5 Atomic update of a data structure

The example below is a resource object, which maintains a count of free items and also list of them. These two slots must stay synchronized.

Old:

(without-interrupts
 (when (plusp (resource-free-item-count resource))
   (decf (resource-free-item-count resource))
   (pop (resource-free-items resource))))

New: use a lock, because more than one slot has to be updated, so cannot be updated with low level atomic operations.

(mp:with-lock ((resource-lock resource))
	      (when (plusp (resource-free-item-count resource))
		(decf (resource-free-item-count resource))
		(pop (resource-free-items resource))))
19.8.4.6 Atomic access to a cache in a hash table

Old:

(without-interrupts
 (or (gethash value *global-hashtable*)
     (setf (gethash value *global-hashtable*)
	   (make-cached-value))))

New: use the hash table lock.

(hcl:with-hash-table-locked 
 *global-hashtable*
 (or (gethash value *global-hashtable*)
     (setf (gethash value *global-hashtable*)
	   (make-cached-value))))

Alternative new: use the hash table lock only if the value is not already cached. This can be faster than the code above, because it avoids locking the hash table for concurrent reads.

(or (gethash value *global-hashtable*)	; probe without the lock
    (hcl:with-hash-table-locked
     *global-hashtable*
     (or (gethash value *global-hashtable*) ; reread with the lock
	 (setf (gethash value *global-hashtable*)
	       (make-cached-value)))))

LispWorks User Guide and Reference Manual - 20 Sep 2017

NextPrevUpTopContentsIndex