Earlier versions of Poly/ML have provided a form of concurrent execution through the Process structure. Version 5.1 introduces new thread primitives in the Thread structure. This structure is modelled on the Posix thread (pthread) package but simplified and modified for ML. The aim is to provide an efficient implementation of parallelism particularly to enable ML programs to make use of multi-core processors while minimising the changes needed to existing code. The Process structure will continue to be available as a library written on top of these primitives but new programs should use the Thread structure directly.
The thread package differs from pthreads in a number of ways. There is no join function to wait for the completion of a thread. This can be written using mutexes and condition variables. Cancellation and signal handling are combined into the interrupt functions. (The Poly/ML Signal structure handles signals for all the threads together). The effect of explicit cancellation is achieved using the interrupt function. This causes an interrupt to be generated in a specific thread. Alternatively an interrupt can be broadcast to all threads. This is most likely to be used interactively to kill threads that appear to have gone out of control. The normal top-level handler for a console interrupt will generate this. Threads can choose how or whether they respond to these interrupts. A thread that is doing processor-intensive work probably needs to be able to be interrupted asynchronously whereas if it is communicating with other threads the presence of asynchronous interrupts makes correct programming difficult.
exception Thread : string
The Thread exception can be raised by various of the functions in the structure if they detect an error.
structure Thread
eqtype thread
The type of a thread identifier.
datatype threadAttribute =
EnableBroadcastInterrupt of bool |
InterruptState of interruptState |
MaximumMLStack of int option
and interruptState =
InterruptDefer |
InterruptSynch |
InterruptAsynch |
InterruptAsynchOnce
The type of a thread attribute. Thread attributes are properties of the thread that are set initially when the thread is created but can subsequently be modified by the thread itself. The thread attribute type may be extended in the future to include things like scheduling priority. The current thread attributes control the way interrupt exceptions are delivered to the thread.
val fork : (unit -> unit) * threadAttribute list -> thread
Fork a thread. Starts a new thread running
the function argument. The attribute list gives initial values for thread attributes
which can be modified by the thread itself. Any unspecified attributes take
default values. The thread is terminated when the thread function returns, if
it raises an uncaught exception or if it calls
val exit : unit -> unit
Terminate this thread.
val isActive : thread -> bool
Test if a thread is still running or has terminated. This function should be used with care. The thread may be on the point of terminating and still appear to be active.
val equal : thread * thread -> bool
Test whether thread ids are the same. This is provided for backwards compatibility
since
val self : unit -> thread
Return the thread identifier for the current thread.
exception Interrupt
val interrupt : thread -> unit
Send an Interrupt exception to a specific thread. When and indeed whether the exception is actually delivered will depend on the interrupt state of the target thread. Raises Thread if the thread is no longer running, so an exception handler should be used unless the thread is known to be blocked.
val broadcastInterrupt : unit -> unit
Send an interrupt exception to every thread which is set to accept it.
val testInterrupt : unit -> unit
If this thread is handling interrupts synchronously, test to see
if it has been interrupted. If so it raises the
val kill : thread -> unit
Terminate a thread. This should be used as a last resort. Normally a thread should be allowed to clean up and terminate by using the interrupt call. Raises Thread if the thread is no longer running, so an exception handler should be used unless the thread is known to be blocked.
val getLocal : 'a Universal.tag -> 'a option
and setLocal : 'a Universal.tag * 'a -> unit
Get and set thread-local store for the calling thread. The store is a tagged associative memory which is initially empty for a new thread. A thread can call setLocal to add or replace items in its store and call getLocal to return values if they exist. The Universal structure contains functions to make new tags as well as injection, projection and test functions.
val setAttributes : threadAttribute list -> unit
Change the specified attribute(s) for the calling thread. Unspecified attributes remain unchanged.
val getAttributes : unit -> threadAttribute list
Get the values of attributes.
val numProcessors : unit -> int
and numPhysicalProcessors : unit -> int option
Return the number of processors that will be used to run threads and the number of physical processors if that is available.
structure Mutex
type mutex
A mutex provides simple mutual exclusion. A thread can lock a mutex and until it unlocks it no other thread will be able to lock it. Locking and unlocking are intended to be fast in the situation when there is no other process attempting to lock the mutex. These functions may not work correctly if an asynchronous interrupt is delivered during the calls. A thread should use synchronous interrupt when using these calls.
val mutex : unit -> mutex
Make a new mutex
val lock : mutex -> unit
Lock a mutex. If the mutex is currently locked the thread is
blocked until it is unlocked. If a thread tries to lock a mutex that
it has previously locked the thread will deadlock.
N.B.
val unlock : mutex -> unit
Unlock a mutex and allow any waiting threads to run. The behaviour if the mutex was not previously locked by the calling thread is undefined.
val trylock : mutex -> bool
Attempt to lock the mutex. Returns true if the mutex was not previously locked and has now been locked by the calling thread. Returns false if the mutex was previously locked, including by the calling thread.
structure ConditionVar
type conditionVar
Condition variables are used to provide communication between threads. A condition variable is used in conjunction with a mutex and usually a reference to establish and test changes in state. The normal use is for one thread to lock a mutex, test the reference and then wait on the condition variable, releasing the lock on the mutex while it does so. Another thread may then lock the mutex, update the reference, unlock the mutex, and signal the condition variable. This wakes up the first thread and reacquires the lock allowing the thread to test the updated reference with the lock held. More complex communication mechanisms, such as blocking channels, can be written in terms of condition variables.
val conditionVar : unit -> conditionVar
Make a new condition variable.
val wait : conditionVar * Mutex.mutex -> unit
Release the mutex and block until the condition variable is signalled. When wait returns the mutex will have been re-acquired.
If the thread is handling interrupts synchronously this function can be interrupted
using the
A thread should never call this function if it may receive an asynchronous
interrupt. It should always set its interrupt state to either
A condition variable should only be associated with one mutex at a time. All the threads waiting on a condition variable should pass the same mutex as argument.
val waitUntil : conditionVar * Mutex.mutex * Time.time -> bool
As wait except that it blocks until either the condition variable is signalled or the time (absolute) is reached. Either way the mutex is reacquired so there may be a further delay if it is held by another thread.
val signal : conditionVar -> unit
Wake up one thread if any are waiting on the condition variable. If there are several threads waiting for the condition variable one will be selected to run and will run as soon as it has re-acquired the lock.
val broadcast : conditionVar -> unit
Wake up all threads waiting on the condition variable.