Asynchronous Locking
Raku actually provides two different locking classes. A
Lock
object provides a very standard
locking mechanism. When .lock
and .unlock
are used or .protect
is called,
you get a section of code that pauses until the lock frees up, runs while
holding the lock, and then frees the lock so other code that might be waiting on
the lock can run.
However, the Lock
class works in such a way that blocks the current thread. As
I’ve pointed out earlier in this advent calendar, the purpose of threads is to
do stuff, so blocking them from running is preventing them from fulfilling their
purpose. Luckily, there is a solution.
In cases where you want locking, but don’t want to burn your threads waiting for
the lock to free up, consider using
Lock::Async
instead of Lock
. It
works very similarly to Lock
, but the .lock
method does not block. Instead,
it returns a Promise
which will be kept
when the lock is free. Code that await
s that Promise
will pause in a way
that allows Raku to reuse the current thread for another task:
class SafeQueue {
has @!queue;
has Lock::Async $!lock .= new;
method enqueue($value) {
await $!lock.lock;
push @!queue, $value;
$!lock.unlock;
}
method dequeue(--> Any) {
$!lock.protect: { shift @!queue }
}
}
The code above demonstrates both the use of .lock
and .unlock
as well as
.protect
. You should always prefer .protect
as the code in enqueue
above
might leave the lock forever held if an exception gets thrown after the lock is
acquired. From the point of view of your program, a .protect
will behave
similarly between Lock
and Lock::Async
, but internally performs an await
on the .lock
method. This means that the thread the code is running on will be
freed to be used by another task that is waiting to be scheduled.
Cheers.