A monitor provides per-instance mutual exclusion for objects. This means that for a given object instance, only one thread can ever be inside its methods at a time. This is achieved by a lock being associated with each object. The lock is acquired automatically at the entry to each method in the monitor. Condition variables are also supported.
A monitor looks like a normal class, but declared with the monitor
keyword.
use OO::Monitors;
monitor IPFilter {
has %!active;
has %!blacklist;
has $.limit = 10;
has $.blocked = 0;
method should-start-request($ip) {
if %!blacklist{$ip} || (%!active{$ip} // 0) == $.limit {
$!blocked++;
return False;
}
else {
%!active{$ip}++;
return True;
}
}
method end-request($ip) {
%!active{$ip}--;
}
}
That's about all there is to it. The monitor meta-object enforces mutual exclusion.
Condition variables are declared with the conditioned
trait on the monitor.
To wait on a condition, use wait-condition
. To signal that a condition has
been met, use meet-condition
. Here is an example of a bounded queue.
monitor BoundedQueue is conditioned(< not-full not-empty >) {
has @!tasks;
has $.limit = die "Must specify a limit";
method add-task($task) {
while @!tasks.elems == $!limit {
wait-condition <not-full>;
}
@!tasks.push($task);
meet-condition <not-empty>;
}
method take-task() {
until @!tasks {
wait-condition <not-empty>;
}
meet-condition <not-full>;
return @!tasks.shift;
}
}
When wait-conditon
is used, the lock is released and the thread blocks until
the condition is met by some other thread. By contrast, meet-condition
just
marks a waiting thread as unblocked, but retains the lock until the method
is over.
Monitors are vulnerable to deadlock, if you set up a circular dependency. Keep object graphs involving monitors simple and cycle-free, so far as is possible.