class Lock

Low-level thread locking primitive

class Lock {}

A Lock is a low-level constructor for ensuring that only one thread works with a certain object at a given time, or runs a piece of code (called the critical section).

my $x = 0;
my $l = Lock.new;
await (^10).map: {
    start {
        $l.protect({ $x++ });
    }
}
say $x;         # OUTPUT: «10␤» 

Locks are re-entrant, that is, a thread that holds the lock can lock it again without blocking.

High-level Perl 6 code should avoid the direct usage of locks, because they are not composable. Instead, high-level constructs such as Promise, Channel and Supply should be used whenever possible.

Methods

method protect

Defined as:

method protect(Lock:D: &code)

Runs &code and makes sure it is only run in one thread at once.

Note that the Lock itself needs to be created outside the portion of the code that gets threaded and it needs to protect. In the first example below, Lock is first created and assigned to $lock, which is then used inside the Promises to protect the sensitive code. In the second example, a mistake is made, the Lock is created right inside the Promise, so the code ends up with a bunch of separate locks, created in a bunch of threads, and thus they don't actually protect the code we want to protect.

# Right: $lock is instantiated outside the portion of the 
# code that will get threaded and be in need of protection 
my $lock = Lock.new;
await ^20 .map: {
    start {
        $lock.protect: {
            print "Foo";
            sleep rand;
            say "Bar";
        }
    }
}
 
# !!! WRONG !!! Lock is created inside threaded area! 
await ^20 .map: {
    start {
        Lock.new.protect: {
            print "Foo"sleep randsay "Bar";
        }
    }
}

method lock

Defined as:

method lock(Lock:D:)

Acquires the lock. If it is currently not available, waits for it.

my $l = Lock.new;
$l.lock;

This construct provides a low-level, OS-backed lock (on most platforms, it is a pthreads mutex, for example). The lock is reentrant, meaning that if you acquire it while already holding it then it just bumps a recursion count. Trying to acquire the lock when something else is holding it will really block a thread. If you block a thread pool thread with this construct, it won't be able to work on anything else in the meantime. Further, an await while holding a lock will not function in a non-blocking manner (as a threadpool await normally would in 6.d).

For that reason, it's better to use protect instead of an explicit lock/unlock. That makes sure that the lock is always released. If you forget to unlock then you'll "lose" the lock and nothing will ever be able to acquire it again, probably resulting in the program deadlocking. If you really want to do the unlock yourself, the safest way is in a LEAVE phaser.

method unlock

Defined as:

method unlock(Lock:D:)

Releases the lock.

my $l = Lock.new;
$l.lock;
$l.unlock;

Please see the description of .lock above.

method condition

Defined as:

my class ConditionVariable {
    method wait();
    method signal();
    method signal_all();
}
 
method condition(Lock:D: --> ConditionVariable:D)

Returns a condition variable. Compare https://web.stanford.edu/~ouster/cgi-bin/cs140-spring14/lecture.php?topic=locks or https://en.wikipedia.org/wiki/Monitor_%28synchronization%29 for background.

my $l = Lock.new;
$l.condition;

Type Graph

Type relations for Lock
perl6-type-graph Lock Lock Any Any Lock->Any Mu Mu Any->Mu

Expand above chart