многопоточность — как на самом деле работают заборы в переполнении стека

Я боролся с пониманием того, как заборы на самом деле заставляют код синхронизироваться.

например, скажем, у меня есть этот код

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;
}

тогда, я слышал, может быть гонка данных. Но почему это?
Почему для освобождения заборов должно следовать атомарное хранилище, а для получения заборов должна предшествовать атомная нагрузка для правильной синхронизации кода?

Я также был бы признателен, если бы кто-нибудь мог предоставить сценарий выполнения, в котором гонка данных вызывает срабатывание утверждения

1

Решение

Никакая реальная гонка данных не является проблемой для вашего второго фрагмента. Этот фрагмент будет в порядке … если бы компилятор в прямом смысле генерировать машинный код из написанного.

Но компилятор может генерировать любой машинный код, который эквивалентен оригинальному в случае однопоточная программа.

Например, компилятор может отметить, что y переменная не изменяется внутри while(!y) цикл, так что он может загрузить эту переменную один раз для регистрации и использовать только этот регистр в следующих итерациях. Итак, если изначально y=false, вы получите бесконечный цикл.

Другая возможная оптимизация — это просто удаление while(!y) цикл, так как он не содержит доступа к летучий или же атомное переменные и не использует синхронизация действия. (Стандарт C ++ говорит, что любая правильная программа должна в конце концов выполните одно из действий, указанных выше, чтобы компилятор мог опираться на этот факт при оптимизации программы).

И так далее.

В более общем смысле стандарт C ++ определяет, что параллельный доступ к любому неатомическое переменная привести к Неопределенное поведение, что похоже на «Гарантия очищается». Вот почему вы должны использовать атомное y переменная.

С другой стороны, переменная x не должен быть атомарным, поскольку доступ к нему не является одновременным из-за ограничений памяти.

2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]