class Lock::Async

A non-blocking, non-re-entrant, mutual exclusion lock
class Lock::Async {}

A Lock::Async instance provides a mutual exclusion mechanism: when the lock is held, any other code wishing to lock must wait until the holder calls unlock on it, which helps against all kinds of issues resulting from data being read and modified simultaneously from different threads.

Unlike Lock, which provides a traditional OS-backed mutual exclusion mechanism, Lock::Async works with the high-level concurrency features of Raku. The lock method returns a Promise, which will be kept when the lock is available. This Promise can be used with non-blocking await. This means that a thread from the thread pool need not be consumed while waiting for the Lock::Async to be available, and the code trying to obtain the lock will be resumed once it is available.

The result is that it's quite possible to have many thousands of outstanding Lock::Async lock requests, but just a small number of threads in the pool. Attempting that with a traditional Lock would not go so well!

There is no requirement that a Lock::Async is locked and unlocked by the same physical thread, meaning it is possible to do a non-blocking await while holding the lock. The flip side of this is Lock::Async is not re-entrant.

While Lock::Async works in terms of higher-level Raku concurrency mechanisms, it should be considered a building block. Indeed, it lies at the heart of the Supply concurrency model. Prefer to structure programs so that they communicate results rather than mutate shared data structures, using mechanisms like Promise, Channel and Supply.

Methods

method lock

method lock(Lock::Async:D: --> 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.

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

Prefer to use protect instead of explicit calls to lock and unlock.

method unlock

method unlock(Lock::Async:D: --> 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).

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

Prefer to use protect instead of explicit calls to lock and unlock. However, if wishing to use the methods separately, it is wise to use a LEAVE block to ensure that unlock is reliably called. Failing to unlock will mean that nobody can ever lock this particular Lock::Async instance again.

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

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, Lock::Async is first created and assigned to $lock, which is then used inside the Promises code to protect the sensitive code. In the second example, a mistake is made, the Lock::Async is created right inside the Promise, so the code ends up with a bunch of different locks, created in a bunch of threads, and thus they don't actually protect the code we want to protect. Modifying an Array simultaneously from different in the second example is not safe and leads to memory errors.

# 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";

The example below demonstrates the wrong approach: without proper locking this code will work most of the time, but occasionally will result in bogus error messages or low-level memory errors:

# !!! 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);
        }
    }
}

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 if the lock had already been locked at another point in the caller chain.

my Lock::Async $lock .= new;
    my Int         $count = 0;
# The lock is unlocked, so the code runs instantly.
    $lock.protect-or-queue-on-recursion({
        $count++
    });
# 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++
        })
    });
# 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++;
        });
    }
say $count; # OUTPUT: 5

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.

my Lock::Async $lock .= new;
    my Int         $count = 0;
$lock.protect-or-queue-on-recursion({
        my Int $count = 0;
# Runs instantly.
        $lock.with-lock-hidden-from-recursion-check({
            $count++;
        });
# 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
        });
say $count; # OUTPUT: 1
    });

See Also

class int

Native integer

class Allomorph

Dual value number and string

class Any

Thing/object

class AST

Abstract representation of a piece of source code

class atomicint

Integer (native storage at the platform's atomic operation size)

class Block

Code object with its own lexical scope

class CallFrame

Captures the current frame state

class Code

Code object

class Collation

Encapsulates instructions about how strings should be sorted

class Compiler

Information related to the compiler that is being used

class Complex

Complex number

class ComplexStr

Dual value complex number and string

class Cool

Object that can be treated as both a string and number

class CurrentThreadScheduler

Scheduler that synchronously executes code on the current thread

class Date

Calendar date

class DateTime

Calendar date with time

class Distribution::Hash

Distribution::Hash

class Distribution::Locally

Distribution::Locally

class Distribution::Path

Distribution::Path

class Distribution::Resource

Every one of the resources installed with a distribution

class Duration

Length of time

class Encoding::Registry

Management of available encodings

class FatRat

Rational number (arbitrary-precision)

class ForeignCode

Rakudo-specific class that wraps around code in other languages (generally NQP)

class Format

Convert values to a string given a format specification

class Formatter

Produce Callable for given format specification

class HyperSeq

An object for performing batches of work in parallel with ordered output

class HyperWhatever

Placeholder for multiple unspecified values/arguments

class Instant

Specific moment in time

class Int

Integer (arbitrary-precision)

class IntStr

Dual value integer and string

class Junction

Logical superposition of values

class Label

Tagged location in the source code

class Macro

Compile-time routine

class Method

Member function

class Mu

The root of the Raku type hierarchy.

class Nil

Absence of a value or a benign failure

class Num

Floating-point number

role Numeric

Number or object that can act as a number

class NumStr

Dual value floating-point number and string

class ObjAt

Unique identification for an object

class Parameter

Element of a Signature

class Perl

Perl related information

class Proxy

Item container with custom storage and retrieval

class RaceSeq

Performs batches of work in parallel without respecting original order.

class Raku

Raku related information

package RakuAST

Namespace for holding RakuAST related classes

class RakuAST::Doc::Block

Contains the information of a RakuDoc block

class RakuAST::Doc::Declarator

Contains the declarator docs of a RakuAST object

class RakuAST::Doc::Markup

Contains the information about RakuDoc markup

class RakuAST::Doc::Paragraph

Contains the information about a RakuDoc paragraph

class Rat

Rational number (limited-precision)

class RatStr

Dual value rational number and string

class Routine

Code object with its own lexical scope and return handling

class Routine::WrapHandle

Holds all information needed to unwrap a wrapped routine.

class Scalar

A mostly transparent container used for indirections

class Signature

Parameter list pattern

class Str

String of characters

class StrDistance

Contains the result of a string transformation.

class Sub

Subroutine

class Submethod

Member function that is not inherited by subclasses

class Telemetry

Collect performance state for analysis

class Telemetry::Instrument::Thread

Instrument for collecting Thread data

class Telemetry::Instrument::ThreadPool

Instrument for collecting ThreadPoolScheduler data

class Telemetry::Instrument::Usage

Instrument for collecting getrusage data

class Telemetry::Period

Performance data over a period

class Telemetry::Sampler

Telemetry instrument pod

Subset UInt

Unsigned integer (arbitrary-precision)

class ValueObjAt

Unique identification for value types

class Variable

Object representation of a variable for use in traits

class Version

Module version descriptor

class Whatever

Placeholder for the value of an unspecified argument

class WhateverCode

Code object constructed by Whatever-priming

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