|
8 | 8 |
|
9 | 9 | A C<Lock::Async> instance provides a mutual exclusion mechanism: when the
|
10 | 10 | 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. |
12 | 13 |
|
13 | 14 | Unlike L<Lock|/type/Lock>, which provides a traditional OS-backed mutual
|
14 | 15 | exclusion mechanism, C<Lock::Async> works with the high-level concurrency
|
@@ -88,38 +89,62 @@ Defined as:
|
88 | 89 |
|
89 | 90 | method protect(Lock::Async:D: &code)
|
90 | 91 |
|
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; |
103 | 114 |
|
104 | 115 | # 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 |
106 | 118 | my $lock = Lock::Async.new;
|
107 |
| - await ^20 .map: { |
108 |
| - start { |
| 119 | + for ^50 -> $thread { |
| 120 | + @threads.push: start { |
109 | 121 | $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); |
113 | 125 | }
|
114 | 126 | }
|
115 | 127 | }
|
116 | 128 |
|
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 { |
120 | 143 | my $lock = Lock::Async.new;
|
121 | 144 | $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); |
123 | 148 | }
|
124 | 149 | }
|
125 | 150 | }
|
|
0 commit comments