Category : Modula II Source Code
Archive   : TASKING.ZIP
Filename : DININGPH.MOD

 
Output of file : DININGPH.MOD contained in archive : TASKING.ZIP

MODULE DiningPhilosophers; (* test for time slicing and locks *)
(*
WARNING: Do not use ctrl-break on this program while it is running.
If you do, DOS may crash!
To stop this program, press any (normal) key and wait for the
program to terminate normally. It may take quite a while
if you are running many tasks.
*)

FROM RandomNumbers IMPORT irand;
FROM Tasks IMPORT NewTask, NextTask;
FROM Locks IMPORT LockType, BusyLock, Lock, Unlock;
FROM DOSlock IMPORT LockDOS, UnlockDOS;
FROM ClockUtilities IMPORT TimeDifference;
FROM TimeDate IMPORT Time, GetTime;
FROM GetPSP IMPORT getarg;
FROM NumberConversion IMPORT CardToString, StringToCard;
FROM ScreenBIOS IMPORT WriteChDTD, ClearScreen, PutCursor, current;
FROM BIOSKeyboard IMPORT Check, Read;

CONST
maxThinkers = 99;
wspSize = 1000; (* work space size (bytes) *)

VAR
numThinkers: CARDINAL;
forks: ARRAY [1 .. maxThinkers] OF LockType;
realLock: LockType; (* IOTRANSFER does not save the state of the 8087 *)
screenLock: LockType;

PROCEDURE thinker; (* main proc for the thinkers processes *)
VAR
id: CARDINAL;
BEGIN
getid(id);
LOOP (* forever *)
msg(id, "think ");
think;
msg(id, "hungry");
getforks(id);
msg(id, "eat ");
eat;
msg(id, "full ");
dropforks(id);
END;
END thinker;

(*
Critical section to assign task id numbers.
*)
VAR
idlock: LockType;
idnums: CARDINAL;

PROCEDURE getid(VAR idnum: CARDINAL);
BEGIN
Lock(idlock);
INC(idnums);
idnum := idnums;
Unlock(idlock);
END getid;

PROCEDURE think;
(* think for 2.0 to 10.0 seconds *)
VAR
thinktime: REAL;
BEGIN
Lock(realLock);
thinktime := FLOAT(rand(800) + 200) / 100.0;
Unlock(realLock);
pause(thinktime);
END think;

PROCEDURE eat;
(* eat for 2.0 to 10.0 seconds *)
VAR
eattime: REAL;
BEGIN
Lock(realLock);
eattime := FLOAT(rand(800) + 200) / 100.0;
Unlock(realLock);
pause(eattime);
END eat;

PROCEDURE getforks(id: CARDINAL);
BEGIN
LOOP
Lock(forks[id]);
(* BusyLock returns TRUE if the lock was already locked *)
IF BusyLock(forks[id MOD numThinkers + 1]) THEN
Unlock(forks[id])
ELSE
EXIT
END;
Lock(forks[id MOD numThinkers + 1]);
IF BusyLock(forks[id]) THEN
Unlock(forks[id MOD numThinkers + 1])
ELSE
EXIT
END;
END;
END getforks;

PROCEDURE dropforks(id: CARDINAL);
BEGIN
Unlock(forks[id]);
Unlock(forks[id MOD numThinkers + 1]);
END dropforks;

PROCEDURE pause(secs: REAL);
VAR
strt, now: Time;
itsdone: BOOLEAN;
BEGIN
GetTimeX(strt);
REPEAT
NextTask;
GetTimeX(now);
Lock(realLock);
itsdone := TimeDifference(strt, now) >= secs;
Unlock(realLock);
UNTIL itsdone;
END pause;

(* random number generator is a non-reentrant critical section too *)
(* think about it! *)
VAR
randlock: LockType;

PROCEDURE rand(lim: CARDINAL): CARDINAL;
VAR
retval: CARDINAL;
BEGIN
Lock(randlock);
retval := irand(lim);
Unlock(randlock);
RETURN retval;
END rand;

(*
MS-DOS is very non-reentrant.
You can't even read the clock and write to the screen at the same time
since both are devices and the device handler handler is non-reentrant
*)
PROCEDURE GetTimeX(VAR t: Time);
BEGIN
LockDOS;
GetTime(t);
UnlockDOS;
END GetTimeX;

PROCEDURE msgn(id: CARDINAL);
VAR
nstr: ARRAY [0 .. 10] OF CHAR;
BEGIN
CardToString(id, nstr, 5);
Lock(screenLock);
PutCursor((id - 1) MOD 25, (id - 1) DIV 25 * 20, 0);
WriteString(nstr);
Unlock(screenLock);
END msgn;

PROCEDURE msg(id: CARDINAL; str: ARRAY OF CHAR);
BEGIN
Lock(screenLock);
PutCursor((id - 1) MOD 25, (id - 1) DIV 25 * 20 + 7, 0);
WriteString(str);
Unlock(screenLock);
END msg;

PROCEDURE WriteString(str: ARRAY OF CHAR);
VAR
i: CARDINAL;
BEGIN
i := 0;
LOOP
IF str[i] = 0c THEN EXIT END;
WriteChDTD(str[i], 7, 0);
INC(i);
IF i > HIGH(str) THEN EXIT END;
END;
END WriteString;

VAR
i: CARDINAL;
ch: CHAR;
str: ARRAY [0 .. 6] OF CHAR;
itsdone: BOOLEAN;

BEGIN (* main initialize *)
ClearScreen(current.attrib);
(* initialize locks by unlocking them *)
Unlock(randlock);
Unlock(idlock);
Unlock(realLock);
Unlock(screenLock);
getarg(1, str);
StringToCard(str, numThinkers, itsdone);
IF NOT itsdone OR (numThinkers < 2) OR (numThinkers > maxThinkers) THEN
numThinkers := 5;
END;
FOR i := 1 TO numThinkers DO (* initialize the forks locks *)
Unlock(forks[i]);
END;
idnums := 0;
FOR i := 1 TO numThinkers DO (* start up the tasks *)
msgn(i);
NewTask(thinker, wspSize);
REPEAT (* optional *)
NextTask;
Lock(idlock);
itsdone := i = idnums;
Unlock(idlock);
UNTIL itsdone;
END;
REPEAT (* Main process loop. Doesn't look like it does much eh? *)
NextTask;
Check(ch, ch, itsdone);
UNTIL itsdone;
(* get DOSlock, termination is a DOS function too! *)
LockDOS;
Read(ch, ch); (* gobble up the pressed key *)
Lock(screenLock);
ClearScreen(current.attrib);
END DiningPhilosophers.