Есть ли у нас гарантия, что любая атомарная запись немедленно сохранит новое значение атомарной переменной в основной памяти?
Итак, я много читал о переупорядочении инструкций и памяти и о том, как мы можем предотвратить это, но у меня до сих пор нет ответа на один вопрос (возможно, потому что я недостаточно внимателен). У меня вопрос: есть ли у нас гарантия, что любая атомарная запись немедленно сохранит новое значение атомарной переменной в основной памяти? Давайте посмотрим на небольшой пример:
std::atomic<bool> x;
std::atomic<bool> y;
std::atomic<int> count;
void WritingValues()
{
x.store(true, std::memory_order_relaxed);
y.store(true, std::memory_order_relaxed);
}
void ReadValues()
{
while( !y.load(std::memory_order_relaxed) );
if( x.load(std::memory_order_relaxed) )
++count;
}
int main()
{
x = false;
y = false;
count = 0;
std::thread tA(WritingValues);
std::thread tB(ReadValues);
tA.join();
tB.join();
assert( count.load() != 0 );
}
Итак, здесь наш assert может определенно срабатывать, так как мы используем std::memory_order_relaxed и не предотвращаем переупорядочение команд (или переупорядочение памяти во время компиляции, я полагаю, это одно и то же). Но если мы поместим некоторый барьер компилятора в WritingValues для предотвращения переупорядочения команд, все ли будет хорошо? Я имею в виду, гарантирует ли x.store(true, std::memory_order_relaxed), что запись этой конкретной атомарной переменной будет осуществляться непосредственно в память без каких-либо задержек? Или x.load(std::memory_order_relaxed) гарантирует, что значение будет считано из памяти, а не из кэша с недопустимым значением? Другими словами, это хранилище гарантирует только атомарность операции и имеет то же поведение памяти, что и обычная неатомарная переменная, или оно также влияет на поведение памяти?
2 ответа
I mean, does x.store(true, std::memory_order_relaxed) guarantees, that the
of that particular atomic variable will be directly into the memory,
without any latency?
Нет, это не так, и на самом деле, с учетом того, что порядок bool и памяти ослаблен, нет "недопустимого" значения, если вы читаете его только один раз, и истина, и ложь в порядке.
Поскольку упорядоченный порядок в памяти явно указывает, что порядок не выполняется. По сути, в вашем случае это означает только то, что после переключения с ложного на истинное значение в какой-то момент станет истинным для всех других процессов, но не указывает, "когда" это произойдет. Таким образом, единственное, в чем вы можете быть уверены, это то, что оно не станет ложным после того, как станет истинным. Но нет никаких ограничений на то, как долго он будет ложным в другом потоке.
Также это гарантирует, что вы не увидите частично записанную переменную в другом потоке, но это вряд ли относится к bools.
Вам нужно использовать aquire и выпустить здесь. И даже это не даст никаких гарантий относительно реальной памяти, только о поведении программы, синхронизация кеша может добиться цели, даже не возвращая данные назад и не всплывая в памяти.
Поскольку все инструкции загрузки и сохранения являются атомарными, каждая из них представляет собой отдельную машинную инструкцию, поэтому два потока никогда не "прерывают друг друга" в "середине" инструкции загрузки или сохранения.
Заголовок вашего вопроса звучит так: "Есть ли у нас гарантия, что любая атомарная запись немедленно сохранит новое значение атомарной переменной в основной памяти?". Но само определение атомарной инструкции состоит в том, что она не может быть прервана переключением контекста, аппаратным прерыванием, программным ожиданием - ничто!
std::memory_order_relaxed
позволяет переупорядочить инструкции в одной функции. Смотрите, например, этот вопрос. Это почти так же, как ваш вопрос, но у вас есть memory_order_relaxed
в ReadValues()
вместо memory_order_acquire
, В этой функции возможно, что спинлок на переменную y
помещается после увеличения счетчика из-за расслабленного состояния (переупорядочивание компилятора и т. д.). В любом случае ASSERT может потерпеть неудачу, потому что y может быть установлен в true, прежде чем x находится в WriteValues()
из-за переупорядочения памяти, разрешенного memory_order_relaxed (ссылаясь на ответ в аналогичном вопросе.