Я боролся с пониманием того, как заборы на самом деле заставляют код синхронизироваться.
например, скажем, у меня есть этот код
bool x = false;
std::atomic<bool> y;
std::atomic<int> z;
void write_x_then_y()
{
x = true;
std::atomic_thread_fence(std::memory_order_release);
y.store(true, std::memory_order_relaxed);
}
void read_y_then_x()
{
while (!y.load(std::memory_order_relaxed));
std::atomic_thread_fence(std::memory_order_acquire);
if (x)
++z;
}
int main()
{
x = false;
y = false;
z = 0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load() != 0);
}
поскольку за разделительным ограждением следует операция атомарного хранилища, а перед защитным ограждением предшествует атомная нагрузка, все синхронизируется, как положено, и утверждение не сработает
но если у не было атомарной переменной, как это
bool x;
bool y;
std::atomic<int> z;
void write_x_then_y()
{
x = true;
std::atomic_thread_fence(std::memory_order_release);
y = true;
}
void read_y_then_x()
{
while (!y);
std::atomic_thread_fence(std::memory_order_acquire);
if (x)
++z;
}
тогда, я слышал, может быть гонка данных. Но почему это?
Почему для освобождения заборов должно следовать атомарное хранилище, а для получения заборов должна предшествовать атомная нагрузка для правильной синхронизации кода?
Я также был бы признателен, если бы кто-нибудь мог предоставить сценарий выполнения, в котором гонка данных вызывает срабатывание утверждения
Никакая реальная гонка данных не является проблемой для вашего второго фрагмента. Этот фрагмент будет в порядке … если бы компилятор в прямом смысле генерировать машинный код из написанного.
Но компилятор может генерировать любой машинный код, который эквивалентен оригинальному в случае однопоточная программа.
Например, компилятор может отметить, что y
переменная не изменяется внутри while(!y)
цикл, так что он может загрузить эту переменную один раз для регистрации и использовать только этот регистр в следующих итерациях. Итак, если изначально y=false
, вы получите бесконечный цикл.
Другая возможная оптимизация — это просто удаление while(!y)
цикл, так как он не содержит доступа к летучий или же атомное переменные и не использует синхронизация действия. (Стандарт C ++ говорит, что любая правильная программа должна в конце концов выполните одно из действий, указанных выше, чтобы компилятор мог опираться на этот факт при оптимизации программы).
И так далее.
В более общем смысле стандарт C ++ определяет, что параллельный доступ к любому неатомическое переменная привести к Неопределенное поведение, что похоже на «Гарантия очищается». Вот почему вы должны использовать атомное y
переменная.
С другой стороны, переменная x
не должен быть атомарным, поскольку доступ к нему не является одновременным из-за ограничений памяти.
Других решений пока нет …