Written in cppmem pseudo code:
int main()
{
atomic_int n = -1;
atomic_int n2 = 0;
{{{
{
n2.store(1, mo_relaxed);
if (n.load(mo_relaxed) != -1)
n.store(1, mo_release);
}
|||
{
n.store(0, mo_release);
int expected = 0;
do
{
desired = n2.load(mo_relaxed);
}
while (!n.compare_exchange_strong(&expected, desired, mo_acquire));
}
}}}
assert(n == 1);
}
In words, two atomic variables are initialized as n = -1 and n2 = 0;
Thread 1 first writes 1 to n2 and then to n, provided n wasn't (still) -1.
Thread 2 first writes 0 to n, then loads n2 and assigns n = n2 as long as n wasn't changed since it last read n (or when n is still 0).
After both threads joined n must equal 1 in every possible execution.
This code is part of an open source project of me and related to resetting a streambuf implementation to the start of the buffer lock-free while two threads concurrently read and write to it. This particular part has to do with 'sync-ing' (or, flushing written output).
I designed this and it works when every operation is sequentially consistent (this was brute force tested), but I can't wrap my head around the memory order requirements :/.
This assertion could fire if instructions (and cache updates) are performed in this order:
n2
from0
to1
.n
from-1
to0
.n2
(inn2.load(mo_relaxed)
). At this point there are no synchronizations so any value previously stored inn2
(including the initialization value, see [intro.race]/1) can be loaded. Let's say it loads0
.n==0
(the last one in the modification order ofn
),n2==0
,expected==0
,desired==0
before the compare exchange instruction. Then, the compare exchange succeeds, and stores0
inn
.At the end of the execution of the two threads you get
n==0
andn2==1
.With sequential consistency what I described cannot happen because if thread 1 saw
n2==1 && n==-1
, thread 2 could not seen2==0 && n==0
.With this algorithm I am sure it is not possible to use any other memory order than sequential consistency.