Skip to content

Commit e696810

Browse files
authored
Merge pull request #3488 from Raku/protecc-example
Add a more realistic Lock::Async.protect example
2 parents a493998 + 89063e1 commit e696810

File tree

1 file changed

+48
-23
lines changed

1 file changed

+48
-23
lines changed

doc/Type/Lock/Async.pod6

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
99
A C<Lock::Async> instance provides a mutual exclusion mechanism: when the
1010
lock is held, any other code wishing to C<lock> must wait until the holder
11-
calls C<unlock>.
11+
calls C<unlock>, which helps against all kinds of issues resulting from
12+
data being read and modified simultaneously from different threads.
1213
1314
Unlike L<Lock|/type/Lock>, which provides a traditional OS-backed mutual
1415
exclusion mechanism, C<Lock::Async> works with the high-level concurrency
@@ -88,38 +89,62 @@ Defined as:
8889
8990
method protect(Lock::Async:D: &code)
9091
91-
Calls C<lock>, does an C<await> to wait for the lock to be available,
92-
and reliably calls C<unlock> afterwards, even if the code throws an
93-
exception.
94-
95-
Note that the L<Lock::Async|/type/Lock::Async> itself needs to be created outside the portion
96-
of the code that gets threaded and it needs to protect. In the first
97-
example below, L<Lock::Async|/type/Lock::Async> is first created and assigned to C<$lock>,
98-
which is then used I<inside> the L<Promises|/type/Promise> to protect
99-
the sensitive code. In the second example, a mistake is made, the
100-
C<Lock::Async> is created right inside the L<Promise|/type/Promise>, so the code ends up
101-
with a bunch of separate locks, created in a bunch of threads, and
102-
thus they don't actually protect the code we want to protect.
92+
This method reliably wraps code passed to C<&code> parameter with a
93+
lock it is called on. It calls C<lock>, does an C<await> to wait for
94+
the lock to be available, and reliably calls C<unlock> afterwards,
95+
even if the code throws an exception.
96+
97+
Note that the L<Lock::Async|/type/Lock::Async> itself needs to be
98+
created outside the portion of the code that gets threaded and it
99+
needs to protect. In the first example below,
100+
L<Lock::Async|/type/Lock::Async> is first created and assigned to
101+
C<$lock>, which is then used I<inside> the L<Promises|/type/Promise>
102+
code to protect the sensitive code. In the second example, a mistake is
103+
made, the C<Lock::Async> is created right inside the
104+
L<Promise|/type/Promise>, so the code ends up with a bunch of different
105+
locks, created in a bunch of threads, and thus they don't actually
106+
protect the code we want to protect. Modifying an Array I<simultaneously>
107+
from different in the second example is not safe and leads to memory errors.
108+
109+
# Compute how many prime numbers there are in first 10 000 of them
110+
# using 50 threads
111+
my @primes = 0 .. 10_000;
112+
my @results;
113+
my @threads;
103114
104115
# Right: $lock is instantiated outside the portion of the
105-
# code that will get threaded and be in need of protection
116+
# code that will get threaded and be in need of protection,
117+
# so all threads share the lock
106118
my $lock = Lock::Async.new;
107-
await ^20 .map: {
108-
start {
119+
for ^50 -> $thread {
120+
@threads.push: start {
109121
$lock.protect: {
110-
print "Foo";
111-
sleep rand;
112-
say "Bar";
122+
my $from = $thread * 200;
123+
my $to = ($thread + 1) * 200;
124+
@results.append: @primes[$from..$to].map(*.is-prime);
113125
}
114126
}
115127
}
116128
117-
# !!! WRONG !!! Lock::Async is instantiated inside threaded area!
118-
await ^20 .map: {
119-
start {
129+
# await for all threads to finish calculation
130+
await Promise.allof(@writers);
131+
# say how many prime numbers we found
132+
say "We found " ~ @results.grep(*.value).elems ~ " prime numbers";
133+
134+
The example below demonstrates the wrong approach: without proper locking
135+
this code will work most of the time, but occasionally will result
136+
in bogus error messages or low-level memory errors:
137+
138+
# !!! WRONG !!! Lock::Async is instantiated inside threaded area,
139+
# so all the 20 threads use 20 different locks, not syncing with
140+
# each other
141+
for ^50 -> $thread {
142+
@threads.push: start {
120143
my $lock = Lock::Async.new;
121144
$lock.protect: {
122-
print "Foo"; sleep rand; say "Bar";
145+
my $from = $thread * 200;
146+
my $to = ($thread + 1) * 200;
147+
@results.append: @primes[$from..$to].map(*.is-prime);
123148
}
124149
}
125150
}

0 commit comments

Comments
 (0)