The basic elements of parallel processing in EULISP are processes and mutual exclusion, which are provided by the classes <thread> and <lock> respectively.
A thread is allocated and initialized, by calling make. The keyword of a thread specifies the initial function, which is where execution starts the first time the thread is dispatched by the scheduler. In this discussion four states of a thread are identified: new, running, aborted and finished. These are for conceptual purposes only and a EuLisp program cannot distinguish between new and running or between aborted and finished. (Although accessing the result of a thread would permit such a distinction retrospectively, since an aborted thread will cause a condition to be signalled on the accessing thread and a finished thread will not.) In practice, the running state is likely to have several internal states, but these distinctions and the information about a thread’s current state can serve no useful purpose to a running program, since the information may be incorrect as soon as it is known. The initial state of a thread is new. The union of the two final states is known as determined. Although a program can find out whether a thread is determined or not by means of wait with a timeout of t (denoting a poll), the information is only useful if the thread has been determined.
A thread is made available for dispatch by starting it, using the function thread-start, which changes its state from new to running. After running a thread becomes either finished or aborted. When a thread is finished, the result of the initial function may be accessed using thread-value. If a thread is aborted, which can only occur as a result of a signal handled by the default handler (installed when the thread is created), then thread-value will signal the condition that aborted the thread on the thread accessing the value. Note that thread-value suspends the calling thread if the thread whose result is sought is not determined.
While a thread is running, its progress can be suspended by accessing a lock, by a stream operation or by calling thread-value on an undetermined thread. In each of these cases, thread-reschedule is called to allow another thread to execute. This function may also be called voluntarily. Progress can resume when the lock becomes unlocked, the input/output operation completes or the undetermined thread becomes determined.
The actions of a thread can be influenced externally by signal. This function registers a condition to be signalled no later than when the specified thread is rescheduled for execution—when thread-reschedule returns. The condition must be an instance of <thread-condition>. Conditions are delivered to the thread in order of receipt. This ordering requirement is only important in the case of a thread sending more than one signal to the same thread, but in other circumstances the delivery order cannot be verified. A signal on a determined thread has no discernable effect on either the signalled or signalling thread unless the condition is not an instance of <thread-condition>, in which case an error is signalled on the signalling thread. See also § 12.8.
A lock is an abstract data type protecting a binary value which denotes whether the lock is locked or unlocked. The operations on a lock are lock and unlock. Executing a lock operation will eventually give the calling thread exclusive control of a lock. The unlock operation unlocks the lock so that either a thread subsequently calling lock or one of the threads which has already called lock on the lock can gain exclusive access.
The programming model is that of concurrently executing threads, regardless of whether the configuration is a multi-processor or not, with some constraints and some weak fairness guarantees.
The parallel semantics are preserved on a sequential run-to-completion implementation by requiring communication between threads to use only thread primitives and shared data protected by locks—both the thread primitives and locks will cause rescheduling, so other threads can be assumed to have a chance of execution.
There is no guarantee about which thread is selected next. However, a fairness guarantee is needed to provide the illusion that every other thread is running. A strong guarantee would ensure that every other thread gets scheduled before a thread which reschedules itself is scheduled again. Such a scheme is usually called “round-robin”. This could be stronger than the guarantee provided by a parallel implementation or the scheduler of the host operating system and cannot be mandated in this definition.
A weak but sufficient guarantee is that if any thread reschedules infinitely often then every other thread will be scheduled infinitely often. Hence if a thread is waiting for shared data to be changed by another thread and is using a lock, the other thread is guaranteed to have the opportunity to change the data. If it is not using a lock, the fairness guarantee ensures that in the same scenario the following loop will exit eventually:
The class of all instances of <thread>.
An object to examine.
The supplied argument if it is an instance of <thread>, otherwise ().
This function takes no arguments.
The result is ().
This function is called for side-effect only and may cause the thread which calls it to be suspended, while other threads are run. In addition, if the thread’s condition queue is not empty, the first condition is removed from the queue and signalled on the thread. The resume continuation of the signal will be one which will eventually call the continuation of the call to thread-reschedule.
thread-value, signal and § 12.8 for details of conditions and signalling.
This function takes no arguments.
The thread on which current-thread was executed.
the thread to be started, which must be new. If thread is not new, an error is signalled (condition class: ??).
values to be passed as the arguments to the initial function of thread.
The thread which was supplied as the first argument.
The state of thread is changed to running. The values obj1 to objn will be passed as arguments to the initial function of thread.
the thread whose finished value is to be accessed.
The result of the initial function applied to the arguments passed from thread-start. However, if a condition is signalled on thread which is handled by the default handler the condition will now be signalled on the thread calling thread-value—that is the condition will be propagated to the accessing thread.
If thread is not determined, each thread calling thread-value is suspended until thread is determined, when each will either get the thread’s value or signal the condition.
Returns () if timeout was reached, otherwise a non-() value.
wait provides a generic interface to operations which may block. Execution of the current thread will continue beyond the wait form only when one of the following happened:
wait returns () if timeout occurs, else it returns a non-nil value.
A timeout argument of () or zero denotes a polling operation. A timeout argument of t denotes indefinite blocking (cases a or c above). A timeout argument of a non-negative integer denotes the minimum number of time units before timeout. The number of time units in a second is given by the implementation-defined constant ticks-per-second.
This code fragment copies characters from stream s to the current output stream until no data is received on the stream for a period of at least 1 second.
threads (section 15.1), streams (section 16.15).
The thread on which to wait.
The timeout period which is specified by one of (), t, and non-negative integer.
Result is either thread or (). If timeout is (), the result is thread if it is determined. If timeout is t, thread suspends until thread is determined and the result is guaranteed to be thread. If timeout is a non-negative integer, the call blocks until either thread is determined, in which case the result is thread, or until the timeout period is reached, in which case the result is (), whichever is the sooner. The units for the non-negative integer timeout are the number of clock ticks to wait. The implementation-defined constant ticks-per-second is used to make timeout periods processor independent.
wait and ticks-per-second (§ 12.8).
The number of time units in a second expressed as a double precision floating point number. This value is implementation-defined.
This is the general condition class for all conditions arising from thread operations.
Signalled if the given continuation is called on a thread other than the one on which it was created.
Signalled by thread-start if the given thread has been started already.
The class of all instances of <lock>. This class has no init-options. The result of calling make on <lock> is a new, open lock.
An object to examine.
The supplied argument if it is an instance of <lock>, otherwise ().
the lock to be acquired.
The lock supplied as argument.
Executing a <lock> operation will eventually give the calling thread exclusive control of lock. A consequence of calling <lock> is that a condition from another thread may be signalled on this thread. Such a condition will be signalled before lock has been acquired, so a thread which does not handle the condition will not lead to starvation; the condition will be signalled continuably so that the process of acquiring the lock may continue after the condition has been handled.
unlock and § 12.8 for details of conditions and signalling.
the lock to be released.
The lock supplied as argument.
The unlock operation unlocks lock so that either a thread subsequently calling <lock> or one of the threads which has already called <lock> on the lock can gain exclusive access.
Place holder for <simple-thread> class.