Rules / Types, classes & roles / basic

Lock::Async Reference

A non-blocking, non-re-entrant, mutual exclusion lock

What it is #

Routines #

Signatures are reproduced from the documentation, including their declared return types. A routine listed here is part of the type's published interface; whether Raku++ implements it is a separate question, answered by the examples below.

method lock #

method lock(Lock::Async:D: --> Promise:D)

Returns Promise:D.

Returns a Promise that will be kept when the lock is available. In the case that the lock is already available, an already kept Promise will be returned. Use await to wait for the lock to be available in a non-blocking manner. Prefer to use protect instead of explicit calls to lock and unlock.

method unlock #

method unlock(Lock::Async:D: --> Nil)

Returns Nil.

Releases the lock. If there are any outstanding lock Promises, the one at the head of the queue will then be kept, and potentially code scheduled on the thread pool (so the cost of calling unlock is limited to the work needed to schedule another piece of code that wants to obtain the lock, but not to execute that code). Prefer to use protect instead of

method protect #

method protect(Lock::Async:D: &code)

This method reliably wraps code passed to &code parameter with a lock it is called on. It calls lock, does an await to wait for the lock to be available, and reliably calls unlock afterwards, even if the code throws an exception. Note that the Lock::Async itself needs to be created outside the portion of the code that gets threaded and it needs to protect. In the first example below,

method protect-or-queue-on-recursion #

method protect-or-queue-on-recursion(Lock::Async:D: &code)

When calling protect on a Lock::Async instance that is already locked, the method is forced to block until the lock gets unlocked. protect-or-queue-on-recursion avoids this issue by either behaving the same as protect if the lock is unlocked or the lock was locked by something outside the caller chain, returning Nil, or queueing the call to &code and returning a Promise

method with-lock-hidden-from-recursion-check #

method with-lock-hidden-from-recursion-check(&code)

Temporarily resets the Lock::Async recursion list so that it no longer includes the lock this method is called on and runs the given &code immediately if the call to the method occurred in a caller chain where protect-or-queue-on-recursion has already been called and the lock has been placed on the recursion list.

Examples, run three ways #

Every example below comes from the official documentation, together with the output that documentation asserts. Each was then executed by Rakudo and by Raku++ when this page was built. Where the three agree, one result is shown; where they do not, all three are — because which of them is wrong is exactly the information worth having.

16 no-output

class Lock::Async {}

Not executed: the documentation states no expected output for this example.

my $l = Lock::Async.new;
await $l.lock;

Not executed: the documentation states no expected output for this example.

my $l = Lock::Async.new;
await $l.lock;
$l.unlock;

Not executed: the documentation states no expected output for this example.

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

Not executed: the documentation states no expected output for this example.

# Compute how many prime numbers there are in first 10 000 of them
# using 50 threads
my @primes = 0 .. 10_000;
my @results;
my @threads;

# Right: $lock is instantiated outside the portion of the
# code that will get threaded and be in need of protection,
# so all threads share the lock
my $lock = Lock::Async.new;
for ^50 -> $thread {
@threads.push: start {
    $lock.protect: {
        my $from = $thread * 200;
        my $to = ($thread + 1) * 200;
        @results.append: @primes[$from..$to].map(*.is-prime);
    }
}
}

# await for all threads to finish calculation
await Promise.allof(@writers);
# say how many prime numbers we found
say "We found " ~ @results.grep(*.value).elems ~ " prime numbers";

Not executed: the documentation states no expected output for this example.

# !!! WRONG !!! Lock::Async is instantiated inside threaded area,
# so all the 20 threads use 20 different locks, not syncing with
# each other
for ^50 -> $thread {
@threads.push: start {
    my $lock = Lock::Async.new;
    $lock.protect: {
        my $from = $thread * 200;
        my $to = ($thread + 1) * 200;
        @results.append: @primes[$from..$to].map(*.is-prime);
    }
}
}

Not executed: the documentation states no expected output for this example.

my Lock::Async $lock .= new;
my Int         $count = 0;

Not executed: the documentation states no expected output for this example.

# The lock is unlocked, so the code runs instantly.
$lock.protect-or-queue-on-recursion({
    $count++
});

Not executed: the documentation states no expected output for this example.

# Here, we have caller recursion. The outer call only returns a Promise
# because the inner one does. If we try to await the inner call's Promise
# from the outer call, the two calls will block forever since the inner
# caller's Promise return value is just the outer's with a then block.
$lock.protect-or-queue-on-recursion({
    $lock.protect-or-queue-on-recursion({
        $count++
    }).then({
        $count++
    })
});

Not executed: the documentation states no expected output for this example.

# Here, the lock is locked, but not by anything else on the caller chain.
# This behaves just like calling protect would in this scenario.
for 0..^2 {
    $lock.protect-or-queue-on-recursion({
        $count++;
    });
}

Not executed: the documentation states no expected output for this example.

say $count; # OUTPUT: 5

Not executed: the documentation states no expected output for this example.

my Lock::Async $lock .= new;
my Int         $count = 0;

Not executed: the documentation states no expected output for this example.

$lock.protect-or-queue-on-recursion({
    my Int $count = 0;

Not executed: the documentation states no expected output for this example.

    # Runs instantly.
    $lock.with-lock-hidden-from-recursion-check({
        $count++;
    });

Not executed: the documentation states no expected output for this example.

    # Runs after the outer caller's protect-or-queue-on-recursion call has
    # finished running.
    $lock.protect-or-queue-on-recursion({
        $count++;
    }).then({
        say $count; # OUTPUT: 2
    });

Not executed: the documentation states no expected output for this example.

    say $count; # OUTPUT: 1
});

Not executed: the documentation states no expected output for this example.