class Lock

A low-level, re-entrant, mutual exclusion lock
class Lock {}

A Lock is a low-level concurrency control construct. It provides mutual exclusion, meaning that only one thread may hold the lock at a time. Once the lock is unlocked, another thread may then lock it.

A Lock is typically used to protect access to one or more pieces of state. For example, in this program:

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

The Lock is used to protect operations on $x. An increment is not an atomic operation; without the lock, it would be possible for two threads to both read the number 5 and then both store back the number 6, thus losing an update. With the use of the Lock, only one thread may be running the increment at a time.

A Lock is re-entrant, meaning that a thread that holds the lock can lock it again without blocking. That thread must unlock the same number of times before the lock can be obtained by another thread (it works by keeping a recursion count).

It's important to understand that there is no direct connection between a Lock and any particular piece of data; it is up to the programmer to ensure that the Lock is held during all operations that involve the data in question. The OO::Monitors module, while not a complete solution to this problem, does provide a way to avoid dealing with the lock explicitly and encourage a more structured approach.

The Lock class is backed by operating-system provided constructs, and so a thread that is waiting to acquire a lock is, from the point of view of the operating system, blocked.

Code using high-level Raku concurrency constructs should avoid using Lock. Waiting to acquire a Lock blocks a real Thread, meaning that the thread pool (used by numerous higher-level Raku concurrency mechanisms) cannot use that thread in the meantime for anything else.

Any await performed while a Lock is held will behave in a blocking manner; the standard non-blocking behavior of await relies on the code following the `await` resuming on a different Thread from the pool, which is incompatible with the requirement that a Lock be unlocked by the same thread that locked it. See Lock::Async for an alternative mechanism that does not have this shortcoming. Other than that, the main difference is that Lock mainly maps to operating system mechanisms, while Lock::Async uses Raku primitives to achieve similar effects. If you're doing low-level stuff (native bindings) and/or actually want to block real OS threads, use Lock. However, if you want a non-blocking mutual exclusion and don't need recursion and are running code on the Raku thread pool, use Lock::Async.

By their nature, Locks are not composable, and it is possible to end up with hangs should circular dependencies on locks occur. Prefer to structure concurrent programs such that they communicate results rather than modify shared data structures, using mechanisms like Promise, Channel and Supply.

Methods

method protect

multi method protect(Lock:D: &code)

Obtains the lock, runs &code, and releases the lock afterwards. Care is taken to make sure the lock is released even if the code is left through an exception.

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 rand; say "Bar";
            }
        }
    }

method lock

method lock(Lock:D:)

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

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

Since a Lock is implemented using OS-provided facilities, a thread waiting for the lock will not be scheduled until the lock is available for it. Since Lock is re-entrant, if the current thread already holds the lock, calling lock will simply bump a recursion count.

While it's easy enough to use the lock method, it's more difficult to correctly use unlock. Instead, prefer to use the protect method instead, which takes care of making sure the lock/unlock calls always both occur.

method unlock

method unlock(Lock:D:)

Releases the lock.

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

It is important to make sure the Lock is always released, even if an exception is thrown. The safest way to ensure this is to use the protect method, instead of explicitly calling lock and unlock. Failing that, use a LEAVE phaser.

my $l = Lock.new;
    {
        $l.lock;
        LEAVE $l.unlock;
    }

method condition

method condition(Lock:D: )

Returns a condition variable as a Lock::ConditionVariable object. Check this article or the Wikipedia for background on condition variables and how they relate to locks and mutexes.

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

You should use a condition over a lock when you want an interaction with it that is a bit more complex than simply acquiring or releasing the lock.

constant ITEMS = 100;
my $lock = Lock.new;
my $cond = $lock.condition;
my $todo = 0;
my $done = 0;
my @in = 1..ITEMS;
my @out = 0 xx ITEMS;

loop ( my $i = 0; $i < @in; $i++ ) {
    my $in := @in[$i];
    my $out := @out[$i];
    Thread.start( {
      my $partial = $in² +1;
      if $partial.is-prime {
          $out = $partial but "Prime";
      } else {
          $out = $partial;
      }
      $lock.protect( {
         $done++;
         $cond.signal if $done == $todo;
      } );
    } );
    $todo++;
}
$lock.protect( {
    $cond.wait({  $done == $todo } );
});

say @out.map: { $_.^roles > 2 ?? $_.Num ~ "*" !! $_ };
# OUTPUT: «2* 5* 10 17* 26 37* 50 65 82 101* … »

In this case, we use the condition variable $cond to wait until all numbers have been generated and checked and also to .signal to another thread to wake up when the particular thread is done.

See Also

class Attribute

Member variable

class Cancellation

Removal of a task from a Scheduler before normal completion

class Channel

Thread-safe queue for sending values from producers to consumers

class CompUnit

CompUnit

class CompUnit::Repository::FileSystem

CompUnit::Repository::FileSystem

class CompUnit::Repository::Installation

CompUnit::Repository::Installation

class Distro

Distribution related information

class Grammar

Formal grammar made up of named regexes

class IO::ArgFiles

Iterate over contents of files specified on command line

class IO::CatHandle

Use multiple IO handles as if they were one

class IO::Handle

Opened file or stream

class IO::Notification

Asynchronous notification for file and directory changes

class IO::Notification::Change

Changes in a file, produced by watch-file

class IO::Path

File or directory path

class IO::Path::Cygwin

IO::Path pre-loaded with IO::Spec::Cygwin

class IO::Path::Parts

IO::Path parts encapsulation

class IO::Path::QNX

IO::Path pre-loaded with IO::Spec::QNX

class IO::Path::Unix

IO::Path pre-loaded with IO::Spec::Unix

class IO::Path::Win32

IO::Path pre-loaded with IO::Spec::Win32

class IO::Pipe

Buffered inter-process string or binary stream

class IO::Socket::Async

Asynchronous socket in TCP or UDP

class IO::Socket::Async::ListenSocket

A tap for listening TCP sockets

class IO::Socket::INET

TCP Socket

class IO::Spec

Platform specific operations on file and directory paths

class IO::Spec::Cygwin

Platform specific operations on file and directory paths for Cygwin

class IO::Spec::QNX

Platform specific operations on file and directory paths QNX

class IO::Spec::Unix

Platform specific operations on file and directory paths for POSIX

class IO::Spec::Win32

Platform specific operations on file and directory paths for Windows

class IO::Special

Path to special I/O device

class Kernel

Kernel related information

class Lock::ConditionVariable

Condition variables used in locks

class Match

Result of a successful regex match

class Pod::Block

Block in a Pod document

class Pod::Block::Code

Verbatim code block in a Pod document

class Pod::Block::Comment

Comment in a Pod document

class Pod::Block::Declarator

Declarator block in a Pod document

class Pod::Block::Named

Named block in a Pod document

class Pod::Block::Para

Paragraph in a Pod document

class Pod::Block::Table

Table in a Pod document

class Pod::Defn

Pod definition list

class Pod::FormattingCode

Pod formatting code

class Pod::Heading

Heading in a Pod document

class Pod::Item

Item in a Pod enumeration list

class Proc

Running process (filehandle-based interface)

class Proc::Async

Running process (asynchronous interface)

class Promise

Status/result of an asynchronous computation

class Regex

String pattern

class Semaphore

Control access to shared resources by multiple threads

class Supplier

Live Supply factory

class Supplier::Preserving

Cached live Supply factory

class Supply

Asynchronous data stream with multiple subscribers

class Tap

Subscription to a Supply

class Thread

Concurrent execution of code (low-level)

class ThreadPoolScheduler

Scheduler that distributes work among a pool of threads

class Unicode

Unicode related information

class VM

Raku Virtual Machine related information

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.