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

19.6 Process Waiting and communication between processes

Process Waiting means that a process suspends its own execution until some condition is true. The generic Process Wait functions take a wait-function argument, which is arbitrary though somewhat restricted Lisp code. A process resumes running when the wait-function returns true. The specific Process Wait functions wait for a specific condition.

19.6.1 Specific Process Wait functions

For communication between processes, these are:

mailbox-read, process-wait-for-event, mailbox-wait and mailbox-wait-for-event.

For synchronization, these are:

condition-variable-wait and barrier-wait, also semaphore-acquire and semaphore-release.

For locking these are:

process-lock, process-exclusive-lock and process-sharing-lock.

For sleeping, these are:

cl:sleep and current-process-pause.

The specific Process Wait functions are designed to reduce latencies and to increase efficiency. In particular, in SMP LispWorks they should be used in preference to the generic Process Wait functions.

19.6.2 Generic Process Wait functions

The generic Process Wait functions are:

Note: For brevity we sometimes refer to "the *-periodic-checks functions" or "the *-with-timeout functions".

All the generic Process Wait functions take wait-reason and wait-function arguments and potentially also arguments to pass to the wait-function. The *-with-timeout functions mentioned above also take a timeout argument. The *-periodic-checks functions also take a period argument.

The wait-reason is used only to mark the process as waiting for something for debugging purposes. It does not affect the behavior of the functions.

The generic Process Wait functions "wake up" (that is, they simply return to the caller) either when the timeout passed (if they take a timeout argument), or when the wait function returns true. The three pairs of functions mentioned above differ in the mechanism that calls the wait function.

process-wait and process-wait-with-timeout arrange that the "scheduler" will call the wait function when it runs. The "scheduler" is invoked at various points, in an indeterminate process. The advantage of this is that the programmer does not need to worry too much about when the wait function is going to be called. In non-SMP LispWorks (that is, LispWorks 5.1 and earlier) the programmer does not need to worry at all: when some process sets up something that would make the wait function return true, the waiter process could not run anyway until the setting-up process stopped for some reason (including preemption), by which time the scheduler would have called the wait function if it had not done it before. In SMP LispWorks (that is, LispWorks 6.0 and later), these two processes can run simultaneously, so the delay between the setting up and the scheduling is not necessary. It can be avoided by "poking" the waiting process with process-poke, if the waiting process is known, or by invoking the scheduler by process-allow-scheduling.

Note: All the specific Process Wait functions, described in 19.6.1 Specific Process Wait functions, record that they wait, and the operations that allow them to continue implicitly "poke" the waiting process. Therefore the specific functions avoid the problem of latency or needing process-poke, and should be be used in preference where possible.

A large disadvantage of process-wait and process-wait-with-timeout is that their wait-function is called by the "scheduler" in an indeterminate process. That means that the wait function does not see the dynamic environment of the calling process (including error handlers), and cannot be debugged properly. It is also called often, and so it needs to be reasonably fast and not allocate much. In addition, having to call the wait function adds overhead to the system. Therefore in general, if you can achieve the required effect by using either any of the specific wait functions or a process-wait-local* function, you should do that and avoid process-wait and process-wait-with-timeout.

process-wait-local and process-wait-local-with-timeout do not have all the disadvantages listed above, but their wait-function is called only when the process is poked (or at the end of the timeout). That means that the programmer does need to worry about when they are called. Typically some other process will set up something, and then poke the waiting process to check that it can run.

Note: if the setting up process always knows for sure whether the waiting process can run, then it is normally simpler to use one of the specific Process Wait functions, or maybe even process-stop and process-unstop.

The *-periodic-checks functions give a partial solution to the question of calling the wait function, by ensuring there is a maximum period of time between calls. If having a bounded delay where a bound of more than 0.1 second is not a problem, then the *-periodic-checks functions are a simple and efficient way to achieve it.

When the delays need to be bounded by a shorter period, either one of the specific Process Wait functions or explicit calls to process-poke need to be used. The latter combined with process-wait-local is the most efficient mechanism, but it does require the programmer to ensure that process-poke is called in all the right places.

19.6.3 Communication between processes and synchronization

The simplest way to pass a specific event between two processes it to use process-wait-for-event on the receiving process, and process-send on the sender side. The "event" that is passed is can be any Lisp object.

process-send and process-wait-for-event use a mailbox to pass the object (the process-mailbox of the receiver). It is possible to use a mailbox object directly, and to communicate between multiple senders and receivers. Use make-mailbox to make a mailbox, and mailbox-send to put a message in it. In some situations there may be an imbalance between sending and receiving messages in a mailbox, which may cause the mailbox to become very big. When this is a problem, you can use mailbox-send-limited to make the sending process wait (or do something else) once the mailbox grows to some limit. There are also functions mailbox-count, mailbox-size and mailbox-full-p to help with these situations.

The receiver(s) use mailbox-wait-for-event, mailbox-wait or mailbox-read. mailbox-wait-for-event should be used on processes that may make windows (including any process associated with a CAPI interface), but can be used elsewhere. mailbox-read is faster, but if it used on a process with a window it may cause hanging.

In general, the receiving process decides hows to interpret an event. However, the system has a "standard" generic function, general-handle-event, to interpret events. general-handle-event has methods that process lists by applying the car to the cdr, and processes function objects or symbols by calling them. There is a method on t that does nothing. You can add your own method on your defined classes (which can be structures).

general-handle-event is used when system code needs to interpret events, most importantly processes that CAPI uses to display windows use it. Hence for processes that use the "standard" event handling, you can send an object using process-send and expect it to be processed by general-handle-event. general-handle-event is also used by process-all-events, which processes all the events for the current process, and wait-processing-events, which waits until some predicate returns true while processing events.

In some situations it is useful to execute some code next time the current process processes events, rather than immediately. That can be achieved by process-send with the current process, or more conveniently by current-process-send.

process-wait-for-event and process-send and mailbox are the primary interface for communication between processes, and should be used unless there is a very good reason to use a different mechanism.

19.6.4 Synchronization

Synchronization can be achieved by the various process-wait* functions with the appropriate wait-function argument, but for simple cases of synchronization it is better to use the synchronization objects: condition-variable or barrier. These synchronization objects are simple, efficient, deal with all thread-safety issues, and ensure that the processes that are ready to run will run immediately, rather than the next time that the wait function is called.

Condition variables (or type condition-variable) are used when one or more processes have the knowledge to control when another process(es) runs. The "ignorant" process(es) use condition-variable-wait to wait until they can continue. The "knowledgeable" process(es) use condition-variable-signal and condition-variable-broadcast to tell the "ignorant" processes when they can run. Because the communication is via the condition variable, the processes do not need to know explicitly about each other. For more details, see 19.7.1 Condition variables.

Barriers (of type barrier) are used (mainly) for symmetric synchronization, when a group of processes needs to ensure that none of them goes too far ahead of the rest. The processes call barrier-wait when they want to synchronize, and barrier-wait waits until the other process arrive too (that is, they call barrier-wait). Barriers have additional features that allow more complex synchronization. For more details, see 19.7.2 Synchronization barriers.

LispWorks® User Guide and Reference Manual - 01 Dec 2021 19:30:21