Skip to content

Commit c92f2f1

Browse files
committed
Document atomic operations on Scalar containers.
1 parent 492a608 commit c92f2f1

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed

β€Ždoc/Type/Scalar.pod6β€Ž

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,156 @@ variables.
8787
CATCH { default { put .^name, ': ', .Str } };
8888
# OUTPUT: «X::Assignment::RO: Cannot modify an immutable Int␀»
8989
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+
90242
=end pod

0 commit comments

Comments
Β (0)