(*********************************************************************)
(* Title:	Threads	of Control - Definitions		     *)
(* LastEdit:	"Tue Oct 30 16:36:05 1984" by Mick Jordan	     *)
(* Author: 	Mick Jordan					     *)
(* 		Copyright (C) 1984 by Acorn Research Centre	     *)
(*********************************************************************)

DEFINITION MODULE Threads;
FROM SYSTEM IMPORT WORD;

EXPORT QUALIFIED
  Thread, ForkeeArg, ForkeeReturn, Forkee, Fork, Join,
  Mutex, Acquire, Release, InitMutex,
  Condition, Wait, Signal, Broadcast, InitCondition;
  

(* Most of the facilities for programming with concurrent threads
of control (sometimes called "lightweight processes") are
provided by the interface "Threads".  Three notions are
important: Thread, Mutex, and Condition.
*)

TYPE REFANY = WORD;                  (* may become a standard type *)

(* A thread of control (Threads.Thread) is created by Fork: *)

TYPE Thread;
TYPE ForkeeArg = REFANY;
TYPE ForkeeReturn = REFANY;
TYPE Forkee = PROCEDURE(ForkeeArg): ForkeeReturn;

PROCEDURE Fork(forkee: Forkee; forkeeArg: ForkeeArg): Thread;

(* Fork creates a new thread of control within the caller's address
space and causes it to execute the indicated Forkee with the
indicated ForkeeArg.  When the Forkee completes execution, its
result may be acquired by Join:
*)

PROCEDURE Join(thread: Thread): ForkeeReturn;

(* Join synchronizes with the specified Thread, waiting, if
necessary, until the Forkee completes execution.
*)

(* Threads synchonize explicitly by means of Mutexes: *)

TYPE Mutex;

PROCEDURE Acquire(VAR mutex: Mutex);
PROCEDURE Release(VAR mutex: Mutex);
PROCEDURE InitMutex(VAR mutex: Mutex);

(* The language provides the following syntax for mutual exclusion:
   *** NOT YET IMPLEMENTED ***

	LOCK <mutex> DO statement-sequence END;

where <mutex> is a designator of type Mutex.  This construct is
equivalent (in pidgin Modula-2+) to:

	VAR &t: SYSTEM.ADDRESS;	(* a non-conflicting name *)
	&t := ADR(<mutex>);		(* evaluate expression only once *)
	Threads.Acquire(LOOPHOLE(&t, VAR Threads.Mutex));
	TRY
	  statement-sequence
	FINALLY
	  Threads.Release(LOOPHOLE(&t, VAR Threads.Mutex))
	END;
*)

(* Threads use Conditions to notify one another of potentially
interesting state changes.
*)

TYPE Condition;

PROCEDURE Wait(VAR mutex: Mutex; VAR condition: Condition);
PROCEDURE Signal(VAR condition: Condition);
PROCEDURE Broadcast(VAR condition: Condition);
PROCEDURE InitCondition(VAR condition: Condition);

(* Wait will suspend execution of the Thread until the specified
Condition is notified.  Signal will awaken at most one Thread
from the suspended state; Broadcast will awaken all Threads
suspended on the Condition.  A Condition will "remember" one
notification if no Thread is suspended when Signal or Broadcast
is invoked. Thus, a Condition is logically equivalent to a
Boolean semaphore.  Wait will Release the Mutex before waiting
and Acquire it again after resuming execution.

Note that the semantics we have adopted specify that a return
from a Wait is merely a hint that the associated Boolean
expression may now be true. The return suggests that the
condition should be retested, and does not guarantee (as in some
schemes) that the condition is true. Thus, Waits should occur in
WHILE loops of the form shown below. As long as programs treat
the return from a Wait as a hint, extra Signals are harmless.

Finally, Mutexes and Conditions must be initialized using the
InitMutex and InitCondition procedures in Threads. If they are
not properly initialized in this way, chaos will likely ensue.
Also, one should never explicitly use a Mutex or Condition
value, e.g. by copying, assigning or by passing as a parameter.
All operations on Mutex's or Conditions take VAR parameters.

The following example illustrates the use of these constructs.

	MODULE ProCon;
	IMPORT Threads;

	CONST BufferMax = 10;

	TYPE
	  BufferIndex = [0..BufferMax-1];
	  BufferObject = RECORD
	    mutex: Threads.Mutex;
	    in, out: BufferIndex;
	    contents: ARRAY BufferIndex OF INTEGER;
	    nonFull, nonEmpty: Threads.Condition
	  END;
	  Buffer = REF BufferObject;

	PROCEDURE Produce(buffer: Buffer; i: INTEGER);
	  BEGIN
	    LOCK buffer^.mutex DO
	      WHILE (buffer^.in+1) MOD BufferMax = buffer^.out DO
	        Threads.Wait(buffer^.mutex, buffer^.nonFull);
	      END;
	      buffer^.contents[buffer^.in] := i;
	      buffer^.in := (buffer^.in+1) MOD BufferMax;
	      Threads.Signal(buffer^.nonEmpty);
	    END;
	  END Produce;

	PROCEDURE Consume(buffer: Buffer): INTEGER;
	  VAR i: INTEGER;
	  BEGIN
	    LOCK buffer^.mutex DO
	      WHILE buffer^.in = buffer^.out DO
	        Threads.Wait(buffer^.mutex, buffer^.nonEmpty);
	      END;
	      i := buffer^.contents[buffer^.out];
	      buffer^.out := (buffer^.out+1) MOD BufferMax;
	      Threads.Signal(buffer^.nonFull);
	    END;
	    RETURN i;
	  END Consume;

	PROCEDURE Producer(x: REFANY): REFANY;
	  VAR
	    buffer: Buffer;
	    i: INTEGER;
	  BEGIN
	    buffer := NARROW(x, Buffer);
	    FOR i := 1 TO 10000 DO Produce(buffer, i) END;
	    Produce(buffer, 0);
	    RETURN NIL;
	  END Producer;

	PROCEDURE Consumer(x: REFANY): REFANY;
	  VAR
	    buffer: Buffer;
	    i: INTEGER;
	  BEGIN
	    buffer := NARROW(x, Buffer);
	    REPEAT i := Consume(buffer) UNTIL i = 0;
	    RETURN NIL;
	  END Consumer;

	(* sample main program *)

	VAR
	  b: Buffer;
	  p, c: Threads.Thread;
	  dummy: REFANY;
	BEGIN
	  NEW(b);
	  WITH b^ DO
	    in := 0; out := 0;
	    Threads.InitCondition(nonEmpty);
	    Threads.InitCondition(nonFull);
	    Threads.InitMutex(mutex);
	  END;
	  p := Threads.Fork(Producer, b);
	  c := Threads.Fork(Consumer, b);
	  dummy := Threads.Join(p);
	  dummy := Threads.Join(c);
	END ProCon.
*)


END Threads.
