program dining_philosophers;
{$mode objfpc}{$modeswitch advancedrecords}
uses
{$IFDEF UNIX}
cthreads,
{$ENDIF}
Classes, SysUtils, DateUtils;
const
PHIL_COUNT = 5;
PHIL_NAMES: array[1..PHIL_COUNT] of string = ('Aristotle', 'Kant', 'Spinoza', 'Marx', 'Russell');
DELAY_RANGE = 10;
DELAY_SCALE = 100;
var
StillDining: Integer = PHIL_COUNT;
type
TFork = record
strict private
FState: DWord;
public
procedure Take; inline;
procedure PutDown; inline;
end;
PFork = ^TFork;
TForks = array[1..PHIL_COUNT] of TFork;
TPhilosopher = class(TThread)
private
FName: string;
FLeftFork,
FRightFork: PFork;
FLifeSpan: Integer;
FAwait: PRtlEvent;
protected
procedure Execute; override;
procedure Delay(aInterval: DWord);
public
constructor Create(const aName: string; aLeftFork, aRightFork: PFork; aLifeSpan: Integer);
destructor Destroy; override;
end;
procedure TFork.Take;
begin
while Boolean(InterlockedExchange(FState, 1)) do
ThreadSwitch;
end;
procedure TFork.PutDown;
begin
InterlockedExchange(FState, 0);
end;
function RandomInterval: Integer; forward;
procedure TPhilosopher.Execute;
var
StartTime: TTime;
begin
WriteLn(FName, ' sits down at the table');
while FLifeSpan > 0 do
begin
WriteLn(FName, ' thinking about the eternal');
StartTime := Time;
Delay(RandomInterval);
WriteLn(FName, ' thought for ', MillisecondsBetween(Time, StartTime), ' ms');
WriteLn(FName, ' is hungry');
FLeftFork^.Take;
FRightFork^.Take;
WriteLn(FName, ' eating');
StartTime := Time;
Delay(RandomInterval);
WriteLn(FName, ' ate for ', MillisecondsBetween(Time, StartTime), ' ms');
WriteLn(FName, ' is full');
FRightFork^.PutDown;
FLeftFork^.PutDown;
Dec(FLifeSpan);
end;
WriteLn(FName, ' leaves the table');
InterlockedDecrement(StillDining);
Terminate;
end;
procedure TPhilosopher.Delay(aInterval: DWord);
begin
RTLEventWaitFor(FAwait, aInterval);
RTLEventResetEvent(FAwait);
end;
constructor TPhilosopher.Create(const aName: string; aLeftFork, aRightFork: PFork; aLifeSpan: Integer);
begin
inherited Create(True);
FName := aName;
FLeftFork := aLeftFork;
FRightFork := aRightFork;
FLifeSpan := aLifeSpan;
FreeOnTerminate := True;
FAwait := RtlEventCreate;
end;
destructor TPhilosopher.Destroy;
begin
RTLEventDestroy(FAwait);
inherited;
end;
var
Philosophers: array[1..PHIL_COUNT] of TPhilosopher;
Forks: TForks;
LifeSpan: Integer = 3;
RandLock: DWord = 0;
function RandomInterval: Integer;
begin
while Boolean(InterlockedExchange(RandLock, 1)) do;
try
Result := Succ(Random(DELAY_RANGE)) * DELAY_SCALE;
finally
InterlockedExchange(RandLock, 0);
end;
end;
procedure AskLifeSpan;
var
s: string;
tmp: Integer;
begin
Write('You may set life span[default 3]: ');
ReadLn(s);
tmp := StrToIntDef(s, 0);
if (Tmp > 0) and (Tmp <> LifeSpan) then
begin
LifeSpan := Tmp;
WriteLn('Ok, life span is ', LifeSpan);
end
else
WriteLn('Life span remains ', LifeSpan);
WriteLn('Now let''s begin');
end;
procedure StartDinner;
var
I: Integer = 1;
begin
Randomize;
Forks := Default(TForks);
WriteLn(LineEnding, 'table is empty', LineEnding);
repeat
Philosophers[I] := TPhilosopher.Create(PHIL_NAMES[I], @Forks[I], @Forks[Succ(I)], LifeSpan);
Inc(I);
until I = PHIL_COUNT;
Philosophers[I] := TPhilosopher.Create(PHIL_NAMES[I], @Forks[1], @Forks[I], LifeSpan);
for I := 1 to PHIL_COUNT do
Philosophers[I].Start;
end;
procedure WaitForEnd;
begin
while StillDining > 0 do
ThreadSwitch;
WriteLn(LineEnding, 'table is empty', LineEnding);
end;
begin
AskLifeSpan;
StartDinner;
WaitForEnd;
Write('Done, press any key to exit...');
ReadLn;
end.