Более близкий к оригиналу Хоаровский Монитор с явно выделенными условиями ожидания (более эффективен, чем предыдущий в случае большого количества различных условий ожидания).
Сначала интерфейс:
Код:
DEFINITION Hoare;
TYPE
Monitor = POINTER TO LIMITED RECORD
(m: Monitor) Enter, NEW;
(m: Monitor) Exit, NEW;
(m: Monitor) FINALIZE-
END;
Condition = POINTER TO LIMITED RECORD
(c: Condition) Signal, NEW;
(c: Condition) Wait, NEW;
(c: Condition) FINALIZE-
END;
PROCEDURE NewMonitor (): Monitor;
PROCEDURE NewCondition (m: Monitor): Condition;
END Hoare.
Пример использования:
Код:
IMPORT Hoare;
TYPE
Element* = INTEGER;
Queue* = POINTER TO LIMITED RECORD
monitor: Hoare.Monitor;
nonEmpty, nonFull: Hoare.Condition;
buffer: ARRAY 3 OF Element;
begin, end, count, enqueued, dequeued: INTEGER;
END;
PROCEDURE NewQueue* (): Queue;
VAR q: Queue;
BEGIN NEW(q);
q.monitor := Hoare.NewMonitor();
q.nonEmpty := Hoare.NewCondition(q.monitor);
q.nonFull := Hoare.NewCondition(q.monitor);
RETURN q
END NewQueue;
PROCEDURE (q: Queue) Enqueue* (e: Element), NEW;
BEGIN
q.monitor.Enter;
WHILE q.count >= LEN(q.buffer) DO q.nonFull.Wait END;
INC(q.count);
INC(q.enqueued);
q.buffer[q.end] := e;
q.end := (q.end + 1) MOD LEN(q.buffer);
q.nonEmpty.Signal;
q.monitor.Exit
END Enqueue;
PROCEDURE (q: Queue) Dequeue* (OUT e: Element), NEW;
BEGIN
q.monitor.Enter;
WHILE q.count < 1 DO q.nonEmpty.Wait END;
DEC(q.count);
INC(q.dequeued);
e := q.buffer[q.begin];
q.begin := (q.begin + 1) MOD LEN(q.buffer);
q.nonFull.Signal;
q.monitor.Exit
END Dequeue;
Вообще, в оригинале Хоар предполагал при Wait использование не WHILE, а IF. Но в его мониторе было требование чтобы процессы ожидавшие по Wait впускались первыми и будились по очереди, здесь же немного не так (будятся все), так что нужен WHILE.
Реализация:
Код:
MODULE Hoare;
IMPORT WinApi;
TYPE
Monitor* = POINTER TO LIMITED RECORD
n: INTEGER;
h: WinApi.HANDLE
END;
Condition* = POINTER TO LIMITED RECORD
m: Monitor;
h: WinApi.HANDLE
END;
PROCEDURE NewMonitor* (): Monitor;
VAR m: Monitor;
BEGIN NEW(m);
m.h := WinApi.CreateEvent(NIL, WinApi.FALSE, WinApi.TRUE, NIL);
RETURN m
END NewMonitor;
PROCEDURE NewCondition* (m: Monitor): Condition;
VAR c: Condition;
BEGIN ASSERT(m # NIL, 20);
NEW(c);
c.m := m;
c.h := WinApi.CreateEvent(NIL, WinApi.TRUE, WinApi.FALSE, NIL);
RETURN c
END NewCondition;
PROCEDURE (m: Monitor) Enter*, NEW;
VAR res: INTEGER;
BEGIN
IF WinApi.InterlockedIncrement(m.n) # 1 THEN
res := WinApi.WaitForSingleObject(m.h, WinApi.INFINITE)
END
END Enter;
PROCEDURE (m: Monitor) Exit*, NEW;
VAR ok: WinApi.BOOL;
BEGIN
IF WinApi.InterlockedDecrement(m.n) # 0 THEN
ok := WinApi.SetEvent(m.h)
END
END Exit;
PROCEDURE (c: Condition) Wait*, NEW;
VAR res: INTEGER;
BEGIN
res := WinApi.SignalObjectAndWait(c.m.h, c.h, WinApi.INFINITE, WinApi.FALSE);
res := WinApi.InterlockedDecrement(c.m.n);
c.m.Enter
END Wait;
PROCEDURE (c: Condition) Signal*, NEW;
VAR ok: WinApi.BOOL;
BEGIN ok := WinApi.PulseEvent(c.h)
END Signal;
PROCEDURE (m: Monitor) FINALIZE-;
VAR ok: WinApi.BOOL;
BEGIN
(* ok := WinApi.CloseHandle(h.pulse); *)
ok := WinApi.CloseHandle(m.h)
END FINALIZE;
PROCEDURE (c: Condition) FINALIZE-;
VAR ok: WinApi.BOOL;
BEGIN
ok := WinApi.CloseHandle(c.h)
END FINALIZE;
END Hoare.