Misc

lock#

Place an advisory lock on a shared variable, array, hash, or subroutine until the lock goes out of scope.

lock is the synchronisation primitive of Perl’s interpreter-threads model (use threads; use threads::shared). When THING is a variable that has been declared :shared — or a reference to a shared aggregate — lock acquires an advisory mutex on that datum for the rest of the enclosing block. When the block exits, the lock is released. Any other thread calling lock on the same datum blocks until the holder releases it.

The lock is advisory: it stops other threads that also call lock on the same variable, nothing else. A thread that just reads or writes the shared variable without locking it is not blocked and not serialised. lock is a convention, not a barrier.

Synopsis#

lock $shared_scalar
lock @shared_array
lock %shared_hash
lock &shared_sub
lock $ref_to_shared_aggregate

What you get back#

The argument itself:

  • For a scalar, the scalar value.

  • For an array, hash, or subroutine, a reference to it.

The return value is rarely used. lock is called for its side effect (acquiring the mutex), not for its value.

Lock is scoped, not statement-scoped#

The lock is released when the innermost enclosing block exits, not when the lock statement finishes. Two consequences:

  • lock inside a { ... } block serialises only that block’s duration. Exiting the block — by fall-through, return, last, die, whatever — releases the lock.

  • You cannot release a lock early. There is no unlock. If you need a shorter critical section, introduce a tighter { ... } block:

    {
        lock %shared_hash;
        $shared_hash{$key} = $value;
    }   # lock released here
    do_unlocked_work();
    

Weak keyword#

lock is a weak keyword. If a subroutine named lock is in scope at the point of the call (declared or imported before the call site), that subroutine is called instead of the built-in. This makes lock safe to use as a method or function name in code that does not use threads. To force the built-in when a same-named sub is in scope, call it via its package: CORE::lock($thing).

Examples#

Serialise a hash update between threads:

use threads;
use threads::shared;

my %counts :shared;

sub bump {
    my $key = shift;
    lock %counts;
    $counts{$key}++;
}

Lock an aggregate through a reference. lock follows one level of reference to find the datum to lock:

my @queue :shared;
my $queue_ref = \@queue;

sub enqueue {
    my $item = shift;
    lock $queue_ref;        # locks @queue
    push @queue, $item;
}

Narrow the critical section with an inner block so unrelated work runs unlocked:

my $result;
{
    lock %cache;
    $result = $cache{$key};
}
expensive_post_processing($result);   # runs without the lock

Without use threads::shared, lock is a no-op. This makes it harmless to leave in code that may run single-threaded:

# no `use threads::shared` here
lock $x;                    # does nothing, does not warn

Edge cases#

  • Unshared variable: lock on a variable that was not declared :shared and is not a reference to shared data does nothing useful. Under upstream this may croak in some cases; under others it is silently a no-op. Do not rely on lock to catch the “forgot to share” bug.

  • Reference vs aggregate: lock \%h and lock %h both lock %h. lock dereferences one level to find the shared datum.

  • Recursive locking within one thread: the same thread may re-acquire a lock it already holds; the mutex is recursive per thread. Another thread still blocks.

  • No unlock: releasing before block exit requires restructuring into a tighter scope. undef $lock_guard patterns from other languages do not apply — lock is not a value you store.

  • Weak-keyword ambiguity: a sub lock declared or imported earlier wins over the built-in. Use CORE::lock to disambiguate.

  • Not cross-process: lock coordinates threads within one interpreter process. For file-level locking across processes use flock; for advisory filesystem locks they are separate mechanisms with no interaction.

Differences from upstream#

  • pperl does not implement interpreter threads. The lock opcode is recognised by the parser but has no runtime effect — it behaves exactly as upstream lock does under a build without threads::shared: a silent no-op returning its argument. Code that uses lock defensively (as the upstream documentation recommends for single-threaded compatibility) runs unchanged. Code that depends on lock for actual synchronisation will not be synchronised under pperl.

  • The companion distribution threads::shared is not available under pperl; use threads::shared fails at compile time. Parallelism in pperl is exposed through JIT auto-parallelisation (Rayon) on specific loop shapes, not through user-visible ithreads.

See also#

  • flock — advisory lock on a filehandle, coordinating between processes rather than threads

  • wait — reap a child process; the process-level equivalent of waiting for another unit of execution

  • fork — the other concurrency primitive in core Perl, with no shared memory by default

  • our — declare a package-scoped variable; commonly paired with :shared in threaded code

  • local — dynamic-scope binding, sometimes confused with lock because both are scope-bounded and both restore on block exit