@@ -87,4 +87,156 @@ variables.
87
87
CATCH { default { put .^name, ': ', .Str } };
88
88
# OUTPUT: Β«X::Assignment::RO: Cannot modify an immutable Intβ€Β»
89
89
90
+ = head1 Atomic Operations on Scalar
91
+
92
+ A C < Scalar > can have its value changed using a hardware-supported atomic
93
+ compare and swap operation. This is useful when implementing lock free data
94
+ structures and algorithms. It may also be fetched and assigned to in an
95
+ "atomic" fashion, which ensures appropriate memory barriering and prevents
96
+ unwanted optimizations of memory accesses.
97
+
98
+ A C < Scalar > that will be used with an atomic operation should B < always > be
99
+ explcitly initialized with a value before any atomic operations are
100
+ performed upon it. This is to avoid races with lazy allocation and
101
+ auto-vivification. For example:
102
+
103
+ cas(@a[5], $expected, $value)
104
+
105
+ Will work in principle since an C < Array > consists of C < Scalar > containers.
106
+ However, the container is only bound into the array upon initial assignment.
107
+ Therefore, there would be a race to do that binding. The C < Scalar > atomic
108
+ operations will never check for or do any such auto-vivification, so as to
109
+ make such bugs much more evident (rather than only observed under stress).
110
+
111
+ = head1 Routines
112
+
113
+ = head2 atomic-assign
114
+
115
+ Defined as:
116
+
117
+ multi sub atomic-assign($target is rw, $value)
118
+
119
+ Performs an atomic assignment of C < $value > into the C < Scalar > C < $target > . The
120
+ C < atomic-assign > routine ensures that any required barriers are performed such
121
+ that the changed value will be "published" to other threads.
122
+
123
+ = head2 atomic-fetch
124
+
125
+ multi sub atomic-fetch($target is rw)
126
+
127
+ Performs an atomic read of the value in the C < Scalar > C < $target > and returns
128
+ the read value. Using this routine instead of simply using the variable
129
+ ensures that the latest update to the variable from other threads will be seen,
130
+ both by doing any required hardware barriers and also preventing the compiler
131
+ from lifting reads. For example:
132
+
133
+ my $started = False;
134
+ start { atomic-assign($started, True) }
135
+ until atomic-fetch($started) { }
136
+
137
+ Is certain to terminate, while in:
138
+
139
+ my $started = False;
140
+ start { atomic-assign($started, True) }
141
+ until $started { }
142
+
143
+ It would be legal for a compiler to observe that C < $started > is not updated in
144
+ the loop, and so lift the read out of the loop, thus causing the program to
145
+ never terminate.
146
+
147
+ = head2 cas
148
+
149
+ Defined as:
150
+
151
+ multi sub cas($target is rw, $expected, $value)
152
+ multi sub cas($target is rw, &operation)
153
+
154
+ Performs an atomic compare and swap of the value in the C < Scalar > C < $target > .
155
+ The first form has semantics like:
156
+
157
+ my $seen = $target;
158
+ if $seen<> =:= $expected<> {
159
+ $target = $value;
160
+ }
161
+ return $seen;
162
+
163
+ Except it is performed as a single hardware-supported atomic instruction, as
164
+ if all memory access to C < $target > were blocked while it took place. Therefore
165
+ it is safe to attempt the operation from multiple threads without any other
166
+ synchronization. Since it is a reference comparison, this operation is usually
167
+ not sensible on value types.
168
+
169
+ For example:
170
+
171
+ constant NOT_STARTED = Any.new;
172
+ constant STARTED = Any.new;
173
+ my $master = NOT_STARTED;
174
+ await start {
175
+ if cas($master, NOT_STARTED, STARTED) === Any {
176
+ say "Master!"
177
+ }
178
+ } xx 4
179
+
180
+ Will reliably only ever print C < Master! > one time, as only one of the threads
181
+ will be successful in changing the C < Scalar > from C < NOT_STARTED > to
182
+ C < STARTED > .
183
+
184
+ The second form, taking a code object, will first do an atomic fetch of the
185
+ current value and invoke the code object with it. It will then try to do an
186
+ atomic compare and swap of the target, using the value passed to the code
187
+ object as C < $exepcted > and the result of the code object as C < $value > . If
188
+ this fails, it will read the latest value, and retry, until a CAS operation
189
+ succeeds.
190
+
191
+ Therefore, an item could be added to the head of a linked list in a lock free
192
+ manner as follows:
193
+
194
+ class Node {
195
+ has $.value;
196
+ has Node $.next;
197
+ }
198
+ my Node $head = Node;
199
+ await start {
200
+ for ^1000 -> $value {
201
+ cas $head, -> $next { Node.new(:$value, :$next) }
202
+ }
203
+ } xx 4;
204
+
205
+ This will reliably build up a linked list of 4000 items, with 4 nodes with
206
+ each value ranging from 0 up to 999.
207
+
208
+ = head1 Operators
209
+
210
+ = head2 infix β=
211
+
212
+ multi sub infix:<β=>($target is rw, $value)
213
+
214
+ Performs an atomic assignment of C < $value > into the C < Scalar > C < $target > . The
215
+ C < β= > operator ensures that any required barriers are performed such that the
216
+ changed value will be "published" to other threads.
217
+
218
+ = head2 prefix β
219
+
220
+ multi sub prefix:<β>($target is rw)
221
+
222
+ Performs an atomic read of the value in the C < Scalar > C < $target > and returns
223
+ the read value. Using this operator instead of simply using the variable
224
+ ensures that the latest update to the variable from other threads will be seen,
225
+ both by doing any required hardware barriers and also preventing the compiler
226
+ from lifting reads. For example:
227
+
228
+ my $started = False;
229
+ start { $started β= True }
230
+ until β$started { }
231
+
232
+ Is certain to terminate, while in:
233
+
234
+ my $started = False;
235
+ start { $started β= True }
236
+ until $started { }
237
+
238
+ It would be legal for a compiler to observe that C < $started > is not updated in
239
+ the loop, and so lift the read out of the loop, thus causing the program to
240
+ never terminate.
241
+
90
242
= end pod
0 commit comments