Мне интересно, возможно ли, чтобы начальное значение в следующем коде могло быть переупорядочено, чтобы быть после вычисления, приводящего к неопределенному поведению.
Следующий пример взят из https://docs.microsoft.com/en-us/dotnet/api/system.threading.interlocked.compareexchange?view=netframework-4.8
public class ThreadSafe
{
// Field totalValue contains a running total that can be updated
// by multiple threads. It must be protected from unsynchronized
// access.
private float totalValue = 0.0F;
// The Total property returns the running total.
public float Total { get { return totalValue; }}
// AddToTotal safely adds a value to the running total.
public float AddToTotal(float addend)
{
float initialValue, computedValue;
do
{
// Save the current running total in a local variable.
initialValue = totalValue;
//Do we need a memory barrier here??
// Add the new value to the running total.
computedValue = initialValue + addend;
// CompareExchange compares totalValue to initialValue. If
// they are not equal, then another thread has updated the
// running total since this loop started. CompareExchange
// does not update totalValue. CompareExchange returns the
// contents of totalValue, which do not equal initialValue,
// so the loop executes again.
}
while (initialValue != Interlocked.CompareExchange(ref totalValue,
computedValue, initialValue));
// If no other thread updated the running total, then
// totalValue and initialValue are equal when CompareExchange
// compares them, and computedValue is stored in totalValue.
// CompareExchange returns the value that was in totalValue
// before the update, which is equal to initialValue, so the
// loop ends.
// The function returns computedValue, not totalValue, because
// totalValue could be changed by another thread between
// the time the loop ends and the function returns.
return computedValue;
}
}
Необходим ли барьер памяти между присвоением totalvalue начальному значению и фактическим вычислением?
Как я понимаю в настоящее время без барьера, его можно оптимизировать таким образом, чтобы удалить начальное значение, что приводит к проблемам безопасности потока, поскольку значение computedValue может быть рассчитано с использованием устаревшего значения, но CompareExchange больше не будет обнаруживать это:
public float AddToTotal(float addend)
{
float computedValue;
do
{
// Add the new value to the running total.
computedValue = totalValue + addend;
// CompareExchange compares totalValue to initialValue. If
// they are not equal, then another thread has updated the
// running total since this loop started. CompareExchange
// does not update totalValue. CompareExchange returns the
// contents of totalValue, which do not equal initialValue,
// so the loop executes again.
}
while (totalValue != Interlocked.CompareExchange(ref totalValue,
computedValue, totalValue));
// If no other thread updated the running total, then
// totalValue and initialValue are equal when CompareExchange
// compares them, and computedValue is stored in totalValue.
// CompareExchange returns the value that was in totalValue
// before the update, which is equal to initialValue, so the
// loop ends.
// The function returns computedValue, not totalValue, because
// totalValue could be changed by another thread between
// the time the loop ends and the function returns.
return computedValue;
}
Есть ли здесь специальные правила для локальных переменных, которые объясняют, почему в примере не используется барьер памяти?