Go to the previous, next section.
Although it is not required by the specification, most Common Lisp implementations include a facility for multiple, independent threads of control (often called lightweight processes) within a single Lisp environment. Unfortunately, this facility is not standardized across the various implementations. Although the capabilities provided are very similar across implementations, the details of lightweight processes and the interface to them differ significantly. This situation makes it difficult to write programs that use lightweight processes and yet are portable across Common Lisp implementations.
Common Lisp ILU does not requires lightweight processes in order to function, but they are useful. In particular, servers typically make heavy use of lightweight process facilities. The purpose of the ILU CL Process Interface is to provide a standardized, portable interface to lightweight processes for use within the ILU environment. This interface isolates ILU users from the differences in the various lightweight process implementations and allows them to write programs that are portable across all implementations to which the ILU CL Process Interface has been ported. At present, these implementations include Franz Allegro CL 4.1, and Lucid Common Lisp 3.0 (a.k.a., Sun Common Lisp).
This chapter explains how the ILU CL Process Interface works for ILU users. It begins with an overview that describes the ILU CL Process model, followed by a listing of some functional capabilities of this model. After brief discussions of the implementation architecture and general limitations of the ILU CL Process Interface, the chapter presents an example of how to use the interface to define a simple shared FIFO queue. Next, it lists all of the functions and macros necessary to use lightweight processes in the ILU environment. The chapter concludes with a brief list of references.
To use the information in this chapter, you should be familiar with Common Lisp and with the notion of processes and threads in an operating system. Familiarity with the UNIX process model would also be helpful. (See the References section for recommendations on further reading.)
The ILU CL Process Interface features an interface to lightweight processes similar to that on the Symbolics Lisp machine. In particular, within a single Lisp environment (which on stock hardware runs as a single heavyweight UNIX process) there are multiple threads of control that can be scheduled independently. These threads are called lightweight processes (or sometimes just processes). Each lightweight process contains its own run-time control and binding stack, but it shares the global data and program address space with all other processes in the Lisp environment. Note that this arrangement differs from that of the UNIX heavyweight process facility, where each process has its own address space as well as its own run-time stack.
Each lightweight process represents an independent thread of control. The multiple threads within the Lisp environment are managed by a special scheduler process. The ILU CL Process Interface makes no assumptions about the nature of this scheduler process. However, most implementations use a time-slice, priority-based scheduler. In such a scheduler, an interrupt occurs once every so often (called the scheduler's quantum). When the interrupt occurs, the process that is currently running is stopped and its state is saved. The scheduler then examines all processes that are runnable (that is, waiting to run) and restarts the process that has the highest priority. This process runs until the next interrupt or until it gives up control to the scheduler, whichever comes first. At any given time, the one process that is "currently" running is known as the current process.
In the ILU CL Process model, each lightweight process is represented by a single Lisp object that maintains the information about that process. Also, each process is always in one of three states: active, inactive, or killed. A process maintains two lists of objects called, respectively, the run reasons and the arrest reasons for the process. For a process to be active, it must have at least one run reason and no arrest reasons. A process with no run reasons or at least one arrest reason is considered inactive. The ILU CL Process Interface provides functions for adding and removing run and arrest reasons for a process. Thus, the user (or a program) can move a process between the active and inactive states.
The scheduler runs only active processes. Until an inactive process is
reactivated, it cannot run. A killed process is one that has been explicitly
killed (using the ilu-process:process-kill
function). A killed
process can never be run again (that is, it can never be made active).
An active process can in turn be in one of two substates: runnable
and waiting. A runnable process is ready to be restarted by the
scheduler, which determines whether and when a process will actually be
restarted based on its status (that is, priority) and the status of the other
runnable processes. A waiting process is a process that has a wait
function and a list of wait arguments. These two items are supplied
to the process using the ilu-process:process-wait
function.
Periodically, the scheduler will apply
the process's wait
function to its wait arguments (in the context of the scheduler). If the
result is a non-nil
value, the wait function and wait arguments are
removed from the process, and the process thereby becomes runnable. Usually,
the scheduler evaluates the wait functions for all waiting processes every
time around the scheduler loop. Therefore, it is important that wait
functions be fast and very efficient.
You can reversably remove a process from a runnable state either by entering a
wait or by making it inactive. In general, it is more efficient to make a
process inactive because this removes it from the scheduler's active process
list. Thus, the scheduler does not incur the cost of periodically evaluating
its wait function. However, an inactive process cannot make itself active.
It must depend on some other process to recognize when it is ready to run
again and to reactivate it at that time. Although a waiting process is
initially more costly than an inactive one, it is automatically returned to a
runnable state by the scheduler whenever its wait function returns
non-nil
. Hence, no second process is needed to restart
a waiting function. Thus, the choice between waiting a process and rendering
it inactive depends on the architecture of the application being written.
When a process is first started, it is given a Lisp function and a set of
arguments to this function. These are known as the process's
initial-function and inital-arguments, respectively. A
newly created process, apply
s its initial-function to its
inital-arguments. When the initial-function returns, the process is
automatically killed. Once killed it can never be restarted. You can also
kill the process before the inital-function returns using the
ilu-process:process-kill
function, which causes the process to
execute a throw
in its current context. This throw
causes the stack to unwind (executing unwind-protect forms along the way) and
the initial-function to return, thereby killing the process.
Every process has a number of properties. Specifically, a process has an arbitrary process name that identifies it in displays and in certain operations. Process names need not be unique. A process also has a priority that the scheduler uses optionally to determine when to schedule the process. Priorities are small integers and default to zero (0). In most implementations, processes with higher priorities are given scheduling preference. Negatives are used to indicate that a process should run as a background task when nothing else is running. Finally, a process has a quantum, which is the amount of time (measured in seconds) that the process wishes to run each time before it is interrupted. In some implementations, the scheduler uses a process's quantum to help determine the actual length of the time-slice given to the process. Many implementations ignore the quantum altogether.
The ILU CL Process Interface also includes a facility called process locks that supports exclusion-based sharing of a common resource (that is, a common object or data structure) or a critical region of code by two or more concurrent processes. A process lock is an object that a process can lock in order to claim exclusive access to the shared resource corresponding to the lock. Process locks are essentially a semaphore mechanism specialized for use with the ILU CL Process interface.
Each process lock has a name and a locker. A lock's name is for display purposes only. Processes can ask to gain or relinquish exclusive rights to the lock (called locking and unlocking the lock, respectively). While a process has rights to the lock, the lock's locker is (generally) the process object for that process. When a process asks to lock a lock that is already locked, the asking process blocks and "waits" until the lock is free. Waiting does not necessarily use the standard wait mechanism. Some implementations use process deactivation to implement the "wait" in this case. Some implementations may also maintain a queue of processes waiting for a lock to be freed, thereby ensuring fair access to the lock. Other implementations may not maintain such a queue, and therefore fair access to the lock is not guaranteed.
Process locks are contractual in nature. The various processes sharing a resource (or critical section of code) must all agree not to access the common resource while the process lock corresponding to that resource is held by another process. Furthermore, they must agree to lock the process lock whenever they need exclusive access to the resource, thereby notifying the other processes of their intent. Moreover, the correspondence between a process lock and the shared resource is a matter of agreement between the cooperating processes. The system does not provide any direct support for this correspondence (although it may be added on at a higher level built on top of the basic process lock mechanism).
Process locks provide a code-centered "sharing" mechanism where the access control is built into the programs that access the shared resource. Process locks are suited for closed, or non-extensible, applications where the shared resource is a standard Lisp data structure (that is, not a CLOS object) and where efficiency is a major concern. For applications not meeting these criteria, a mechanism in which a CLOS object itself controls simultaneous access to its internal data structures may be more appropriate.
The ILU CL Process Interface provides all of the functions and macros necessary to use lightweight processes in the ILU environment. The functionality provided by these functions and macros includes:
The ILU CL Process Interface is implemented as a veneer over
the existing process interfaces for a number of Common Lisp implementations
(currently Franz Allegro CL and Lucid Common Lisp).
In many cases, the implementation's functions are simply imported
and then exported from the ilu-process
package.
In other cases, a new function is wrapped around the
implementation's native function to change the name, arguments, or semantics
of the function so that they match those required by the ILU CL
Process Interface specification. In a few cases, whole new functions have
been written to achieve functionality not provided by the original
implementation.
The nature of the process object in the ILU CL Process Interface is not specified. The process object is inherited from the underlying implementation and may therefore be a list, a structure, a flavor object, or even a CLOS object. Because of this lack of specification, process objects cannot be specialized. Moreover, they cannot be accessed or modified in any way other than through the functional interface described in this chapter.
The ILU CL Process Interface assumes that the scheduler is
loaded and running in the ILU environment. Procedures for
starting the scheduler are not included in the ILU CL Process
Interface. Some implementations, however, may require you to actually load
and start up the scheduler. For example, in Franz Allegro CL, you need to
evaluate (mp:start-scheduler)
either at the top-level or in your
`.clinit.cl' file in order to load and start up the scheduler.
The ILU CL Process Interface is subject to all of the limitations of its underlying implementations. In particular, one problem with most Common Lisp implementations on stock hardware is that the smallest scheduler quantum possible is one second. This means that each process gets to run for one second uninterrupted. For applications that involve real-time response, waiting for one second before an event can be handled is problematic. In practice, this problem can be lessened if all processes release control to the scheduler at regular, short intervals (that is, each few times around a tight inner loop), thereby making the effective quantum significantly less than one second. Note that this practice effectively reduces the scheduler to a prioritized, cooperative scheduler rather than the preemptive scheduler intended.
Most Common Lisp implementations build their process mechanism on top of a very powerful mechanism called stack groups. Stack groups provide for alternative run-time stacks in the Lisp environment that can be used for various purposes beyond implementing processes. For example, stack groups are an ideal substrate for implementing co-routines. Unfortunately, not all implementations provide an interface to stack groups (if indeed they have stack groups). Hence, an interface to stack groups is not a part of the ILU CL Process Interface.
The ILU CL Process Interface is intended as a programmer's
interface; the functions and macros provided should be used to implement
programs that run in the ILU environment. Although you can use
any of the functions and macros directly from a Lisp listener, the interface
is not designed particularly well for interactive use. The two exceptions to
this rule are the functions ilu-process:show-process
and
ilu-process:show-all-processes
, both of which are designed to
print out status information in the Lisp listener window. Because it is a
user-oriented function, ilu-process:show-process
accepts either
the process name or a process object to identify the process whose status is
to be displayed.
Most implementations include an interactive interface to multiple processes and the scheduler. For example, Franz Allegro CL has a special top-level command language that is operative in every Lisp listener. This command language includes the following commands that deal specifically with lightweight processes (see Chapter 4 of [Franz-92] for more information):
:processes
Lists all processes (see ilu-process:all-processes
)
:kill
Kills a process (see ilu-process:process-kill
)
:arrest
Adds an arrest reason to a process (see
ilu-process:add-arrest-reason
)
:unarrest
Removes any arrest reason that was added to a process by :arrest
(see ilu-process:process-revoke-arrest-reason
)
:focus
Performs an :arrest
on a process and arranges for all user keyboard
input to be sent to the arrested process (usually to the debugger).
The following example illustrates how to use the ILU CL Process Interface to define a shared FIFO queue. Two processes will utilize this queue. A producer process will read input items from the user and place them on the shared queue. A consumer process will wake up every five seconds and read items from the shared queue, printing them on the standard output stream as they are taken off the queue. Access to the shared queue will be controlled using a process lock associated with the queue.
;;;________________________________________________ ;;; the shared queue, its process-lock, and its accessors/mutators ;;; (defvar queue (list t) "The shared queue") (defvar queue-lock (ilu-process:make-process-lock :name "queue lock") "process lock for queue") (defun queue-pop (queue) "Pop an item off of the shared FIFO queue. Use ilu-process:with-process-lock to prevent collisions between processes. " (ilu-process:with-process-lock (queue-lock) (prog1 (cadr queue) (rplacd queue (cddr queue))) )) (defun queue-push (queue item) "Push an item onto the shared FIFO queue. Use ilu-process:with-process-lock to prevent collisions between processes. " (ilu-process:with-process-lock (queue-lock) (nconc queue (list item)) )) (defun queue-empty-p (queue) "Is queue empty? Use ilu-process:with-process-lock to prevent collisions between processes. " (ilu-process:with-process-lock (queue-lock) (null (cdr queue)) )) ;;;________________________________________________ ;;; The producer function ;;; (defun produce () "Loop reading an item from the user and pushing it onto the shared queue." (let (Item) (loop ;; Wait until there is something on the input stream. (ilu-process:process-wait "Waiting for input" #'listen *standard-input*) ;; Read the input. (setq Item (read *standard-input*)) ;; Check to see if it is the EOF marker and exit if so. (when (eq Item :EOF) (return nil)) ;; Push the item onto the queue. (queue-push queue Item) ))) ;;;________________________________________________ ;;; The consumer function ;;; (defun consume () "Wake up every five seconds and see if there is something on the shared queue. If there is, pop it off and print it on standard output. If the queue is empty and the producer process is not alive, terminate. " (loop ;; Check to see if there is anything on the queue. (if (not (queue-empty-p queue)) ;; There is an item on the queue; pop and print all items. (do ()((queue-empty-p queue)) (fresh-line t) (princ "Output: ") (prin1 (queue-pop queue)) (fresh-line t) (finish-output t)) ;; Queue is empty; check to see if the producer is still alive. (if (null (ilu-process:find-process "Producer Process")) ;; Producer not alive; terminate. (return nil))) ;; Sleep for five seconds; this gives up control immediately ;; so some other process can run. (sleep 5) )) ;;;________________________________________________ ;;; Main function; starts consumer and producer processes ;;; (defun test-queue () "Start consumer and producer processes. Wait in an idle loop until both the producer and the consumer processes die. This function is meant to be evaluated in the Lisp listener. Waiting until both processes die ensures that the Lisp listener does not interfere with user input to the producer. " (let (Producer Consumer) ;; Start the producer first; the consumer needs the producer to run. (setq Producer (ilu-process:fork-process "Producer Process" #'produce)) ;; Start the consumer. (setq Consumer (ilu-process:fork-process "Consumer Process" #'consume)) ;; Show processes on the standard ouput. (ilu-process:show-all-processes) ;; Wait until both consumer and producer are dead. (ilu-process:process-wait "Waiting for godot" #'(lambda (P1 P2) (not (or (ilu-process:process-alive-p P1) (ilu-process:process-alive-p P2)))) Consumer Producer) ))
The following is a transcript of this test program in operation:
;;;________________________________________________ #73: (test-queue) -------------Data on all processes follows--------- Process: "Consumer Process" Process-alive-p: T Process-active-p: T Process-quantum: 2 Process-priority: 0 Process-run-reasons: (:START :START) Process: "Producer Process" Process-alive-p: T Process-active-p: T Process-quantum: 2 Process-priority: 0 Process-run-reasons: (:START :START) Process: "Initial Lisp Listener" Process-alive-p: T Process-active-p: T Process-quantum: 2 Process-priority: 0 Process-run-reasons: (:ENABLE) 123 Output: 123 456 789 Output: 456 Output: 789 444 555 666 Output: 444 Output: 555 Output: 666 :eof NIL #74:
The following sections detail the functions and macros that make up the
ILU CL Process Interface. All are assumed to be in the ilu-process
package unless otherwise specified. Arguments are shown with their type,
if they have any restrictions on their type. Return types are shown
if the function returns a value. Optional arguments are shown with their
type and their default value.
The following listings describe the object that is used to represent each lightweight process.
A Lisp object representing a single process. This object is to be used only as a handle for the process. To alter the state or characteristics of a process, use the external function interface defined below. The exact nature of the process object differs between implementations. In particular, it may or may not be a flavor or a CLOS object. Hence, it is not safe to specialize processes.
Function: find-process (NAME string
) => process
Returns the process object whose name is NAME. Only
ilu-process:process-alive-p
processes (that is, processes on the
list returned from ilu-process:all-processes
) are searched. This
function returns nil
if there is no matching process.
Function: processp OBJECT => boolean
Returns non-nil
if OBJECT is an object of type
process
for this implementation. This function returns
nil
otherwise.
The following functions and macros provide status information about the general state of processes and the scheduler in the Lisp environment.
Macro: active-processes => list
Returns a list of all active processes; that is, processes that have at least one run reason and no arrest reasons. Note, however, that these processes are not necessarily runnable because they may be in a process-wait.
Returns a list of all processes currently known by the scheduler, including active and inactive processes but not processes that have been killed.
Macro: current-process => process
Returns the process object for the current thread of control.
Function: show-all-processes &optional (STREAM streamp
cl:*standard-output*
) (VERBOSE boolean
nil
)
Displays information about all processes known by the scheduler (that is, the
processes returned by ilu-process:all-processes
). Output is to
STREAM, which defaults to the value of cl:*standard-output*
.
This function shows only non-nil
fields unless VERBOSE is
non-nil
; the default is nil
.
Function: fork-process (NAME-OR-KEY-LIST (or string proplist)
(FUNCTION function
) &rest ARGS => process
Creates a new process and returns the process
object for this
process. In this process, FUNCTION is apply
ed to
ARGS. If FUNCTION ever returns, the process is
automatically killed. The FUNCTION is known as the initial-function
of the process (see ilu-process:process-initial-form
).
The new process is activated by default, although you can create it in a
deactivated state by giving it a run reasons list with a value of
nil
or by giving it one or more arrest reasons as detailed below.
NAME-OR-KEY-LIST is either a string, in which case it serves as the name of the process, or it is a property list with one or more of the following property-value pairs:
:name
(string
)
A string to be used as the name of the process.
:priority
(integer
)
Sets the priority of the process to the given value (see
ilu-process:process-priority
).
:quantum
((or numberp nil)
)
Sets the quantum of the process to the given value (see
ilu-process:process-quantum
). Defaults to 1
.
:stack-size
(fixnum
)
Sets the stack-size of the process (if possible in this implementation).
:run-reasons
(list
)
Sets the run reasons of this process to the given list. Unless run-reasons is
non-nil
, the forked process does not run until a
ilu-process:process-add-run-reason
is done. This property
defaults to (quote (:start))
.
:arrest-reasons
(list
)
Sets the arrest reasons of this process to the given list. If arrest-reasons
is non-nil
, the forked process does not run until a
ilu-process:process-revoke-arrest-reason
is done. This property
defaults to nil
.
:bindings
(list
)
A list of bindings (as in let) that are done in the context of the forked
process before the function is run. This property defaults to
ilu-process:*default-process-bindings*
.
Function: process-kill (PROCESS process
)
Terminates PROCESS and removes it from the scheduler's consideration
altogether. It is an error if PROCESS is not
ilu-process:processp
and
ilu-process:process-alive-p
.
A process may not terminate immediately. In particular, the process is first
activated and scheduled. It is then forced to throw
out of its
initial-function, thereby properly unwinding and executing any unwind forms.
A killed process cannot be reactivated.
Function: process-wait (WHOSTATE string
) (FUNCTION function
) &rest ARGS
The current process is suspended until FUNCTION applied to
ARGS returns non-nil
. During this time, the process's
whostate (see ilu-process:process-whostate
) is set to
WHOSTATE.
Note that the current process is not deactivated. It is simply not scheduled
to run until its wait-function returns non-nil
. The scheduler
re-evaluates the wait-function periodically. In general, the re-evaluation
occurs whenever the waited process would be scheduled to run if it were not
suspended. However, in some implementations it is run during every scheduler
break.
Function: process-add-arrest-reason (PROCESS process
) OBJECT
Adds OBJECT to the list of arrest reasons for PROCESS.
The OBJECT argument can be any Lisp object. It is an error if
PROCESS is not ilu-process:processp
.
Adding an arrest reason may cause a process to become deactivated. In particular, if this is the first arrest reason, then the process becomes deactivated (if it was previously activated).
Function: process-add-run-reason (PROCESS process
) OBJECT
Adds OBJECT to the list of run reasons for PROCESS. The
OBJECT argument can be any Lisp object. It is an error if
PROCESS is not ilu-process:processp
.
Adding a run reason may cause a process to become activated. In particular, if there are no arrest reasons and the added run reason is first, the process goes from a deactivated state to an activated state.
Function: process-arrest-reasons (PROCESS process
) => list
Returns the list of arrest reasons for PROCESS. It is an error if
PROCESS is not ilu-process:processp
.
Function: process-disable (PROCESS process
)
Causes PROCESS to become inactive by removing all of its arrest
reasons and all of its run reasons. It is an error if PROCESS is
not ilu-process:processp
.
Function: process-enable (PROCESS process
)
Causes PROCESS to become active by removing all of its arrest
reasons and all of its run reasons and then giving it a single run reason
(usually :enable
). It is an error if PROCESS is not
ilu-process:processp
.
Function: process-revoke-arrest-reason (PROCESS process
) OBJECT
Removes OBJECT from the list of arrest reasons for
PROCESS. It is an error if PROCESS is not
ilu-process:processp
. OBJECT is compared to the
existing arrest reasons using an eq
test.
Revoking an arrest reason may cause a process to become activated. In particular, when the last arrest reason for a process is removed, the process is (re-)activated if it has at least one run reason.
Function: process-revoke-run-reason (PROCESS process
) OBJECT
Removes OBJECT from the list of run reasons for PROCESS.
It is an error if PROCESS is not ilu-process:processp
.
The OBJECT argument is compared to the existing run reasons using an
eq
test.
Revoking a run reason may cause a process to become inactive. In particular, when the last run reason for a process is removed, the process is made inactive (if it was previously activate).
Function: process-run-reasons (PROCESS process
)
Returns the list of run reasons for PROCESS. It is an error if
PROCESS is not ilu-process:processp
.
Function: process-active-p (PROCESS process
) => boolean
Returns non-nil
if PROCESS is an active process object;
that is, a process with no arrest reasons and at least one run reason.
Otherwise, this function returns nil
. It is an error if
PROCESS is not ilu-process:processp
.
Function: process-alive-p (PROCESS process
) => boolean
Returns non-nil
if PROCESS is alive (that is, has been
created but has not been killed). Essentially, a process is alive if it is on
the list returned by ilu-process:all-processes
. It is an error if
PROCESS is not ilu-process:processp
.
Function: process-initial-form (PROCESS process
) => consp
Returns the initial-form of the process
object PROCESS. It
is an error if PROCESS is not ilu-process:processp
.
Note that the returned value is not an eval
able form. It is
merely the cons
of the process's intial function onto a list of
the initial arguments passed to the function. (See
ilu-process:fork-process
.)
Function: process-name (PROCESS process
) => string
Returns the name of the process
object PROCESS. It is an
error if PROCESS is not ilu-process:processp
. The
ilu-process:process-active-p
function can be used with
setf
to change the name of a process.
Function: process-priority (PROCESS process
) => integer
Returns the scheduling priority for the process
object
PROCESS. It is an error if PROCESS is not
ilu-process:processp
. The
ilu-process:process-priority
function can be used with
setf
to change the priority of a process.
When the priorities are set, a small integer is generally used. Process priorities default to zero (0). Processes with higher priorities are given scheduling preference. Priorities can be negative if a process should run as a background task when nothing else is running.
Note that an implementation is free to ignore process priorities. Setting a
process's priority is merely advisory. For this reason, the value returned by
ilu-process:process-priority
may not match the most recent
setf
on ilu-process:process-priority
.
Function: process-quantum (PROCESS process
) => (or numberp nil)
Returns the quantum, which is the amount of time the scheduler allows a
process to run each time its is rescheduled, for the process
object
PROCESS. It is an error if PROCESS is not
ilu-process:processp
. The
ilu-process:process-quantum
function can be used with
setf
to change the quantum of a process.
The quantum is measured in seconds (not necessarily integral).
Note that an implementation is free to ignore process quantums. Setting a
quantum is merely advisory. For this reason, the value returned by
ilu-process:process-quantum
may not match the most recent
setf
on ilu-process:process-quantum
.
The default process quantum is 1.
Function: process-wait-args (PROCESS process
) => list
Returns a list of the arguments being passed to the wait-function of the
process
object PROCESS. It is an error if PROCESS
is not ilu-process:processp
. (See
ilu-process:process-wait
.)
Function: process-wait-function (PROCESS process
) => (or functionp nil)
Returns the wait-function of the process
object PROCESS.
It is an error if PROCESS is not ilu-process:processp
.
(See ilu-process:process-wait
.)
Function: process-whostate (PROCESS process
) => string
Returns the whostate of the process
object PROCESS. It is
an error if PROCESS is not ilu-process:processp
. The
ilu-process:process-whostate
function can be used with
setf
to change the whostate of a process. (See also
ilu-process:fork-process
and
ilu-process:process-wait
.)
Function: show-process &optional (PROCESS process
) (STREAM streamp
cl:*standard-output*
) (VERBOSE boolean
nil
)
Displays information about process PROCESS, which may be a
process
object or the name of a process known to the scheduler. If
PROCESS is a symbol, it is downcased and converted to a string.
PROCESS defaults to the current process. Output is is to
STREAM, which defaults to the value of cl:*standard-output*
.
If VERBOSE is nil
(defaults to non-nil
), then
only non-nil
fields are displayed and the process's initial-form is
not shown.
Function: process-allow-schedule
Suspends the current process and returns to the scheduler. All other processes of equal or higher priority have a chance to run before control returns to the current process.
Function: process-interrupt (PROCESS process
) (FUNCTION function
) &rest ARGS
Forces PROCESS to apply FUNCTION to ARGS when it is next scheduled. When FUNCTION returns, PROCESS resumes execution where it was interrupted.
In general, ilu-process:process-interrupt
is run immediately
(that is, when PROCESS is next scheduled) if PROCESS is
active, even if PROCESS is a process-wait. If PROCESS is
not active, ilu-process:process-interrupt
may wait until
PROCESS is reactivated before FUNCTION is executed.
Macro: without-scheduling &body BODY
Evaluates the forms in BODY with scheduling turned off. While the
current-process is within the scope of
ilu-process:without-scheduling
, no other process will run.
However, scheduling may be resumed if a ilu-process:process-wait
or ilu-process:process-allow-schedule
is executed within the
scope of ilu-process:without-scheduling
. Most Common Lisp I/O
functions as well as the function sleep
usually call some form
of ilu-process:process-allow-scheduling
and hence will resume
scheduling if called within the scope of a ilu-process:without-scheduling
.
Type: ilu-process:process-lock
The process lock object. You should access fields of this lock using only the functional interface listed in this section.
Function: make-process-lock &optional (NAME (or nil string)
nil
) => process-lock
Creates and returns a process-lock
object with NAME as the
name of the lock.
Function: process-lock (LOCK ilu-process:process-lock
) &optional (LOCK-VALUE process
(ilu-process:current-process)
) (WHOSTATE (or nil string)
)
Grabs LOCK, entering LOCK-VALUE as the lock's locker.
LOCK-VALUE defaults to the current process. It is an error if
LOCK is not ilu-process:process-lock-p
.
If LOCK is already locked, then the current process waits until it
is unlocked. The waiting is done using
ilu-process:process-wait
. The WHOSTATE argument is a
string that is used as the whostate of the process if the process is forced to
wait; defaults to an implementation-dependent string.
Function: process-lock-locker (LOCK ilu-process:process-lock
) => t
Returns the current locker of LOCK. It is an error if LOCK
is not ilu-process:process-lock-p
. This function returns
nil
if LOCK is currently unlocked, that is, has no
locker. This value is not setfable. You should use
ilu-process:process-lock
to set the locker.
Setf-able Function: process-lock-name (LOCK ilu-process:process-lock
) => (or nil string)
Returns the name associated with LOCK. It is an error if
LOCK is not ilu-process:process-lock-p
. The
ilu-process:process-lock-locker
function can be used with
setf
to change the name of a process lock.
Function: process-lock-p OBJECT => boolean
Returns non-nil
if OBJECT is a
ilu-process:process-lock
. Otherwise, this function returns
nil
.
Function: process-unlock (LOCK ilu-process:process-lock
) &optional (LOCK-VALUE t
(ilu-process:current-process)
) (ERROR-P boolean
nil
)
Releases LOCK. It is an error if LOCK is not
ilu-process:process-lock-p
.
If LOCK's locker is not eq
to LOCK-VALUE,
which defaults to the current process, then an error is signalled unless
ERROR-P is nil
(it defaults to t
).
Macro: with-process-lock (LOCK ilu-process:process-lock
) (NORECURSIVE boolean
nil
) &body BODY
Locks LOCK for the current process and evaluates the forms in
BODY. It is an error if LOCK is not
ilu-process:process-lock-p
.
If NORECURSIVE is t
(the default), and if the current
process already owns the lock (determined dynamically), then no action is
taken. If NORECURSIVE is non-nil
, then an error is
signalled if the current process already owns LOCK.
If LOCK is held by another process, then the current process waits
as in ilu-process:process-lock
, which is described earlier in this
section.
Errors in most of the process functions will cause a break. There are no special tricks to handling these errors.
The :focus
command is an important tool for using the Allegro CL
debugger in a multiple-process environment. In particular, in Allegro CL a
new process by default shares its standard input/output (I/O) stream with the
Lisp listener. Generally, this is not a problem because the process runs in
the background and does no I/O. However, if the process enters a break, the
debugger needs to use the process's standard I/O stream to interact with the
user. This could lead to a problem because the debugger I/O from the broken
process will be interleaved with the Lisp listener's normal I/O. Specifically,
the system will not be able to determine to which process user input is
directed.
To avoid this situation, Allegro CL has the notion of a focus
process. Input coming from the shared Lisp listener I/O stream is always sent
to the focused process. Usually this is the Lisp listener process. However,
if a background process breaks, you can use the :focus
command to
focus on the broken process and allow you to send input to the debugger
running in that process. When the debugging is complete, :focus
is
automatically returned to the Lisp listener process.
The following transcript illustrates the use of the :focus
command
in Allegro CL:
;;;_______________________________________________________________________ ;;; Start out focused on the Lisp listener process. List all processes. <cl 71> :processes "Initial Lisp Listener" is active. ;;;_______________________________________________________________________ ;;; Second, start a test process that will enter a break immediately. <cl 72> (ilu-process:fork-process "test" #'error "test break") #<process test #x13e92b1> <cl 73> ;;;________________________________________________ ;;; Process test enters a break. Error: test break ;;; Still speaking to the Lisp listener process, list the processes. [1] <cl 1> :processes "test" is waiting for terminal input. "Initial Lisp Listener" is active. <cl 74> ;;;________________________________________________ ;;; Now refocus on the test process. <cl 74> :focus "test" Focus is now on the "test" process. ;;; Look at stack of test process. <cl 75> :zoom Evaluation stack of process "test": ->(EXCL::STM-SY-READ-CHAR #<synonym stream for *TERMINAL-IO* #x13e9619>) (PEEK-CHAR NIL #<synonym stream for *TERMINAL-IO* #x13e9619> NIL :EOF NIL) (ERROR "test break") (ILU-PROCESS::PROCESS-INITIALIZATION-FUNCTION NIL #<Function ERROR #x219ab9> ("test break")) ;;;________________________________________________ ;;; Kill the test process (which is the current process). <cl 76> :kill Do you really want to kill process "test" [n]? y ;;; Automatic refocus to Lisp listener. Ask listener to list all processes. Focus is now on the "listener" process. <cl 77> :processes "Initial Lisp Listener" is active.
For more information on the Lisp listener interface and the Lisp debugger, see the manual for the implementation of Common Lisp that you are using. For Allegro CL, refer to chapters 4 and 5 of [Franz-92].
It is possible for a process to do a non-blocking attempt to lock a process lock using the following idiom:
(ilu-process:without-scheduling ; Make sure this is not interrupted. (if (ilu-process:process-lock-locker LOCK) ; Is lock free? (ilu-process:process-lock LOCK))) ; Lock is free, grab it. (if (eq ; Did we get the lock for this process? (ilu-process:process-lock-locker LOCK) (ilu-process:current-process)) (prog1 ; Yes, do A, releasing lock on way out. ...A... (ilu-process:process-unlock LOCK)) ...B... ; No, do B, which does not depend on lock. )
[Franz-92]: Allegro Common Lisp User Guide. Release 4.1. Berkeley, CA: Franz Incorporated, March 1992.
Bach, M.J., The Design of the UNIX Operating System. Englewood Cliffs, NJ: Prentice-Hall, 1986. Especially read Chapters 6, 7, and 8.
Deitel, H.M. An Introduction to Operating Systems. Reading, MA: Addison-Wesley, 1984. Especially read Part 2.
Kernighan, B.W. and R. Pike., The UNIX Programming Environment. Englewood Cliffs, NJ: Prentice-Hall, 1984. Especially read Sections 1.4 and 7.4.
Tanenbaum, A.S. Operating Systems: Design and Implementation. Englewood Cliffs, NJ: Prentice-Hall, 1987. Especially read Chapter 2.
Go to the previous, next section.