What would be the minimally needed memory barriers in the following scenario?
Several threads update the elements of an array int a[n] in parallel.
All elements are initially set to zero.
Each thread computes a new value for each element; then,
it compares the computed new value to the existing value stored in the array,
and writes the new value only if it is greater than the stored value.
For example, if a thread computes for a[0] a new value 5, but
a[0] is already 10, then the thread should not update a[0].
But if the thread computes a new value 10, and a[0] is 5,
then the thread must update a[0].
The computation of the new values involves some shared read-only data; it does not involve the array at all.
While the above-mentioned threads are running, no other thread accesses the array. The array is consumed later, after all the threads are guaranteed to finish their updates.
The implementation uses a compare-and-swap loop, wrapping the elements
into atomic_ref (either from Boost or from C++20):
for (int k = 0; k != n; ++k) // For each element of the array
{
// Locally in this thread, compute the new value for a[k].
int new_value = ComputeTheNewValue(k);
// Establish atomic access to a[k].
atomic_ref<int> memory(a[k]);
// [Barrier_0]: Read the existing value.
int existing_value = memory.load(memory_order_relaxed);
while (true) // The compare-and-swap loop.
{
// Overwrite only with higher values.
if (new_value <= existing_value)
break;
// Need to update a[k] with the higher value "new_value", but
// only if a[k] still stores the "existing_value".
if (memory.compare_exchange_weak(existing_value, new_value,
/*Barrier_1*/ memory_order_relaxed,
/*Barrier_2*/ memory_order_relaxed))
{
// a[k] was still storing "existing_value", and it has been
// successfully updated with the higher "new_value".
// We're done, and we may exit the compare-and-swap loop.
break;
}
else
{
// We get here in two cases:
// 1. a[k] was found to store a value different from "existing_value", or
// 2. the compare-and-swap operation has failed spuriously.
// In the first case, the new value stored in a[k] has been loaded
// by compare_exchange_weak() function into the "existing_value" variable.
// Then, we need to compare the "new_value" produced by this thread
// with the newly loaded "existing_value". This is achieved by simply continuing the loop.
// The second case (the spurious failure) is also handled by continuing the loop,
// although in that case the "new_value <= existing_value" comparison is redundant.
continue;
}
}
}
This code involves three memory barriers:
Barrier_0inmemory.load().Barrier_1, to use in read-modify-write whencompare_exchange_weak()succeeds.Barrier_2, to use in load operation whencompare_exchange_weak()fails.
In this scenario, is the code guaranteed to update only with higher values
when all three bariers are set to relaxed?
If not, what minimal barriers are needed to guarantee the corrrect behavior?