In the following code, the exchange operation would succeed when the value
is 0. The return value would be null. If it’s not 0, then its current value is returned:
if (@cmpxchgWeak(&value, 0, 123, .monotonic)) |current_value| {
switch (current_value) {
0 => {
// sporadic failure
},
else => {
// expected failure
},
}
}
So what happens when a sporadic failure (which would not happen if the strong version of the builtin is used instead) occurs? Would current_value
be 0? Or perhaps it can never be 0?
According to the documentation it is equivalent to the following code:
fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_value: T) ?T {
const old_value = ptr.*;
if (old_value == expected_value and usuallyTrueButSometimesFalse()) {
ptr.* = new_value;
return null;
} else {
return old_value;
}
}
Here we can see that on sporadic failures it goes into the else branch and returns the old value, so in your example current_value
would be 0.
If the pseudo-code is correct, then the following would sporadically deadlock:
while (true) {
if (mutex_value.cmpxchgWeak(0, 123, .acquire, .monotonic)) |current_value| {
// sleep until mutex_value is not current_value
std.Thread.wait(&mutex_value, current_value);
} else break;
}
Because std.Thread.wake()
would only be called when the mutex is released (mutex_value = 0), std.Thread.wait()
would never see a change if current_value
is 0.
Yeah, it would deadlock.
Where did you get that code anyways? Neither of Thread.wait nor Thread.wake exist in std.
Oh, I left out “Futex”. std.Thread.Futex.wait
and std.Thread.Futex.wake
.
Anyway, I decided to check the Godbolt output for aarch64 (cmpxchg is always strong in x64) just to be sure. Here’s the input code:
const std = @import("std");
var ai = std.atomic.Value(i32).init(8);
export fn change() i32 {
if (ai.cmpxchgWeak(0, 1, .acquire, .monotonic)) |value| {
return value;
} else {
return 2;
}
}
And the output:
change:
adrp x9, example.ai
add x9, x9, :lo12:example.ai
ldaxr w8, [x9]
cbz w8, .LBB0_2
mov w9, wzr
clrex
b .LBB0_3
.LBB0_2:
mov w10, #1
stxr w11, w10, [x9]
cmp w11, #0
csetm w9, eq
.LBB0_3:
tst w9, #0x1
mov w9, #2
csel w0, w9, w8, ne
ret
example.ai:
.word 8
It does appear that the pseudo code is correct. STXR is the instruction that can fail. The earlier result from LDAXR would then be selected by CSEL, which is going to be 0.
So it’s necessary to make the call to wait()
conditional on current_value != 0
.