<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <TITLE>The Thread structure and signature</TITLE> </HEAD> <BODY BGCOLOR="#ffffff"> <H2><STRONG><font face="Arial, Helvetica, sans-serif">Thread structure</font></STRONG></H2> <P>Earlier versions of Poly/ML have provided a form of concurrent execution through the <a href="Processes5.html">Process</a> 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. </P> <P>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.</P> <PRE><STRONG>signature</STRONG> THREAD = <STRONG>sig</STRONG> <STRONG>exception</STRONG> Thread <STRONG>of</STRONG> string <STRONG>structure</STRONG> Thread: <STRONG>sig</STRONG> <STRONG>type</STRONG> thread; <STRONG>datatype</STRONG> threadAttribute = EnableBroadcastInterrupt <STRONG>of</STRONG> bool | InterruptState <STRONG>of</STRONG> interruptState <STRONG>and</STRONG> interruptState = InterruptDefer | InterruptSynch | InterruptAsynch | InterruptAsynchOnce <STRONG>val</STRONG> fork: (unit->unit) * threadAttribute list -> thread <STRONG>val</STRONG> exit: unit -> unit <B>val</B> isActive: thread -> bool <STRONG>val</STRONG> equal: thread * thread -> bool <STRONG>val</STRONG> self: unit -> thread <STRONG>exception</STRONG> Interrupt <STRONG>val</STRONG> interrupt: thread -> unit <STRONG>val</STRONG> broadcastInterrupt: unit -> unit <STRONG>val</STRONG> testInterrupt: unit -> unit <B>val</B> kill: thread -> unit <STRONG>val</STRONG> getLocal: 'a Universal.tags -> 'a option <STRONG>val</STRONG> setLocal: 'a Universal.tag * 'a -> unit <STRONG>val</STRONG> setAttributes: threadAttribute list -> unit <STRONG>val</STRONG> getAttributes: unit -> threadAttribute list <STRONG>val</STRONG> numProcessors: unit -> int <STRONG>end</STRONG> <STRONG>structure</STRONG> Mutex: <STRONG>sig</STRONG> <STRONG>type</STRONG> mutex <STRONG>val</STRONG> mutex: unit -> mutex <STRONG>val</STRONG> lock: mutex -> unit <STRONG>val</STRONG> unlock: mutex -> unit <STRONG>val</STRONG> trylock: mutex -> bool <STRONG>end</STRONG> <STRONG>structure</STRONG> ConditionVar: <STRONG>sig</STRONG> <STRONG>type</STRONG> conditionVar <STRONG>val</STRONG> conditionVar: unit -> conditionVar <STRONG>val</STRONG> wait: conditionVar * Mutex.mutex -> unit <STRONG>val</STRONG> waitUntil: conditionVar * Mutex.mutex * Time.time -> bool <STRONG>val</STRONG> signal: conditionVar -> unit <STRONG>val</STRONG> broadcast: conditionVar -> unit <STRONG>end</STRONG> <STRONG>end</STRONG>;</PRE> <H3> The Thread substructure</H3> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>exception</STRONG> Thread <STRONG>of</STRONG> string</PRE><BLOCKQUOTE STYLE="margin-bottom: 0cm"> The Thread exception can be raised by various of the functions in the structure if they detect an error.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>type</STRONG> thread</PRE><BLOCKQUOTE STYLE="margin-bottom: 0cm"> The type of a thread identifier.</BLOCKQUOTE> <PRE><STRONG>datatype</STRONG> threadAttribute = EnableBroadcastInterrupt <STRONG>of</STRONG> bool | InterruptState <STRONG>of</STRONG> interruptState <STRONG>and</STRONG> interruptState = InterruptDefer | InterruptSynch | InterruptAsynch | InterruptAsynchOnce</PRE> <BLOCKQUOTE>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.</BLOCKQUOTE> <BLOCKQUOTE STYLE="margin-bottom: 0cm">EnableBroadcastInterrupt controls whether the thread will receive an interrupt sent using broadcastInterrupt or as a result of pressing the console interrupt key. If this is false the thread will not receive them. The default for a new thread if this is not specified is false.</BLOCKQUOTE> <BLOCKQUOTE STYLE="margin-bottom: 0cm">InterruptState controls when and whether interrupts are delivered to the thread. This includes broadcast interrupts and also interrupts directed at a specific thread with the interrupt call. InterruptDefer means the thread will not receive any interrupts. However, if the thread has previously been interrupted the interrupt may be delivered when the thread calls setAttributes to change its interrupt state. InterruptSynch means interrupts are delivered synchronously. An interrupt will be delayed until an interruption point. An interruption point is one of: testInterrupt, ConditionVar.wait, ConditionVar.waitUntil and various library calls that may block, such as IO calls, pause etc. N.B. Mutex.lock is not an interruption point even though it can result in a thread blocking for an indefinite period. InterruptAsynch means interrupts are delivered asynchronously i.e. at a suitable point soon after they are triggered. InterruptAsynchOnce means that only a single interrupt is delivered asynchronously after which the interrupt state is changed to InterruptSynch. It allows a thread to tidy up and if necessary indicate that it has been interrupted without the risk of a second asynchronous interrupt occurring in the handler for the first interrupt. If this attribute is not specified when a thread is created the default is InterruptSynch.</BLOCKQUOTE> <PRE> <STRONG>val</STRONG> fork: (unit->unit) * threadAttribute list -> thread</PRE> <BLOCKQUOTE STYLE="margin-bottom: 0cm"> 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 "exit".</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> exit: unit -> unit</PRE> <BLOCKQUOTE STYLE="margin-bottom: 0cm"> Terminate this thread.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><B>val</B> isActive: thread -> bool</PRE> <BLOCKQUOTE STYLE="margin-bottom: 0cm"> Test if a thread is still running or has terminated.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> equal: thread * thread -> bool</PRE> <BLOCKQUOTE STYLE="margin-bottom: 0cm"> Test whether thread values denote the same thread.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> self: unit -> thread</PRE><BLOCKQUOTE> Return the thread identifier for the current thread.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>exception</STRONG> Interrupt = SML90.Interrupt</PRE><BLOCKQUOTE> The Interrupt exception can be generated in another thread either by a broadcast to all threads or directed to a single thread.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> interrupt: thread -> unit</PRE><BLOCKQUOTE> 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.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> broadcastInterrupt: unit -> unit</PRE><BLOCKQUOTE> Send an interrupt exception to every thread which is set to accept it.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> testInterrupt: unit -> unit</PRE><BLOCKQUOTE> If this thread is handling interrupts synchronously, test to see if it has been interrupted. If so it raises the Interrupt exception.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><B>val</B> kill: thread -> unit</PRE><BLOCKQUOTE> 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 function. Raises Thread if the thread is no longer running, so an exception handler should be used unless the thread is known to be blocked.</BLOCKQUOTE> <PRE><STRONG>val</STRONG> getLocal: 'a Universal.tas -> 'a option <STRONG>val</STRONG> setLocal: 'a Universal.tag * 'a -> unit</PRE> <BLOCKQUOTE> 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.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> setAttributes: threadAttribute list -> unit</PRE><BLOCKQUOTE> Thread attributes are initially set when the thread is forked but can be changed by thread itself using this call. Unspecified attributes remain unchanged.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> getAttributes: unit -> threadAttribute list</PRE> <BLOCKQUOTE>Get the values of attributes for the current thread.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> numProcessors: unit -> int</PRE> <BLOCKQUOTE> Return the number of processors configured on the machine.</BLOCKQUOTE> <H3><font face="Arial, Helvetica, sans-serif">The Mutex substructure</font></H3> <P STYLE="margin-bottom: 0cm">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. Mutexes are non-recursive: if a thread tries to lock a mutex that it has already locked it will deadlock. Note: a thread should never attempt to lock or unlock a mutex if it may receive an asynchronous interrupt. It should always set its interrupt state to either InterruptDefer or InterruptSynch before calling these functions. An asynchronous interrupt may leave the mutex in an indeterminate state.</P> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>type</STRONG> mutex</PRE><BLOCKQUOTE> The type of a mutex</BLOCKQUOTE> <PRE><STRONG>val</STRONG> mutex: unit -> mutex</PRE> <BLOCKQUOTE>Create a new mutex.</BLOCKQUOTE> <STRONG>val</STRONG> lock: mutex -> unit</PRE> <BLOCKQUOTE> 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. "lock" is not an interruption point (a point where synchronous interrupts are delivered) even though a thread can be blocked indefinitely. If the thread attempting to lock the mutex is handling interrupts asynchronously an asynchronous interrupt may be delivered before or after the lock is taken.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> unlock: mutex -> unit</PRE><BLOCKQUOTE> 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.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> trylock: mutex -> bool</PRE><BLOCKQUOTE> Attempt to lock the mutex without blocking. 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.</BLOCKQUOTE> <H3><font face="Arial, Helvetica, sans-serif">The ConditionVar substructure</font></H3> <P STYLE="margin-bottom: 0cm">Condition variables. 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. </P> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>type</STRONG> conditionVar</PRE><BLOCKQUOTE> The type of a condition variable</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> conditionVar: unit -> conditionVar</PRE><BLOCKQUOTE> Make a new condition variable.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> wait: conditionVar * Mutex.mutex -> unit</PRE> <BLOCKQUOTE> <p>Release the mutex and block until the condition variable is signalled. When wait returns the mutex will have been re-acquired.</p> <p>If the thread is handling interrupts synchronously this function can be interrupted using the "Thread.interrupt" function or, if the thread is set to accept broadcast interrupts, "Thread.broadcastInterrupt". The thread will re-acquire the mutex before the exception is delivered. An exception will only be delivered in this case if the interrupt is sent before the condition variable is signalled. If the interrupt is sent after the condition variable is signalled the function will return normally even if it has not yet re-acquired the mutex. The interrupt state will be delivered on the next call to "wait", "Thread.testInterrupt" or other blocking call.</p> <p>A thread should never call this function if it may receive an asynchronous interrupt. It should always set its interrupt state to either InterruptSynch or InterruptDefer beforehand. An asynchronous interrupt may leave the condition variable and the mutex in an indeterminate state and could lead to deadlock.</p> </BLOCKQUOTE> <BLOCKQUOTE> <p>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.</p> </BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> waitUntil: conditionVar * Mutex.mutex * Time.time -> bool</PRE> <BLOCKQUOTE> 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. Returns true if the condition variable had been signalled and false if the time had expired before the variable was signalled.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> signal: conditionVar -> unit</PRE> <BLOCKQUOTE> 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.</BLOCKQUOTE> <PRE STYLE="margin-bottom: 0.5cm"><STRONG>val</STRONG> broadcast: conditionVar -> unit</PRE><BLOCKQUOTE> Wake up all threads waiting on the condition variable.</BLOCKQUOTE> <BLOCKQUOTE STYLE="margin-left: 0cm"><BR><BR> </BLOCKQUOTE> </BODY> </HTML>