Возможно ли, что магазин с memory_order_relaxed никогда не достигнет других потоков?
Предположим, у меня есть поток A, который пишет в atomic_int x = 0;
, с помощью x.store(1, std::memory_order_relaxed);
, Без каких-либо других методов синхронизации, сколько времени потребуется, чтобы другие потоки могли это увидеть, используя x.load(std::memory_order_relaxed);
? Возможно ли, что значение записано в x
остается полностью ориентированным на потоки, учитывая текущее определение модели памяти C/C++, которое дает стандарт?
Практический случай, который я имею под рукой, - это когда поток B читает atomic_bool
часто проверять, должен ли он выйти; Другой поток в какой-то момент пишет true для этого bool, а затем вызывает join() в потоке B. Очевидно, я не против вызвать join() до того, как поток B сможет даже увидеть, что atomic_bool установлен, и не против, когда поток B уже видел изменения и завершил выполнение, прежде чем я вызову join(). Но мне интересно: используя memory_order_relaxed
с обеих сторон, возможно ли вызвать join() и заблокировать "навсегда", потому что изменение никогда не распространяется в поток B?
редактировать
Я связался с Марком Бэтти (мозгом математической проверки и последующего исправления требований к модели памяти C++). Первоначально о чем-то другом (который оказался известной ошибкой в cppmem и его тезисе; поэтому, к счастью, я не стал полностью дураком и воспользовался возможностью, чтобы спросить его об этом тоже; его ответ был:
Q: Может ли теоретически быть, что такое хранилище [memory_order_relaxed без (любой последующей) операции освобождения] никогда не достигнет другого потока?
Марк: Теоретически, да, но я не думаю, что это наблюдалось.
В: Другими словами, разве расслабленные хранилища не имеют никакого смысла, если вы не объедините их с какой-либо операцией освобождения (и получите данные в другом потоке), предполагая, что вы хотите, чтобы другой поток это увидел?
Марк: Практически во всех сценариях использования для них используется релиз и приобретение, да.
2 ответа
Это то, что стандарт говорит в 29.3.12:
Реализации должны сделать атомные хранилища видимыми для атомных нагрузок в течение разумного периода времени.
Там нет никакой гарантии store
станет видимым в другом потоке, нет гарантированного времени и нет формальных отношений с порядком памяти.
Конечно, на каждой обычной архитектуре store
станет видимым, но на редких платформах, которые не поддерживают когерентность кэша, он может никогда не стать видимым дляload
,
В этом случае вам придется обратиться к элементарной операции чтения-изменения-записи, чтобы получить последнее значение в порядке изменения.
Это все, что стандарт должен сказать по этому вопросу, я считаю:
[intro.multithread] / 28 Реализация должна обеспечивать, чтобы последнее значение (в порядке изменения), назначенное атомарной операцией или операцией синхронизации, стало видимым для всех других потоков за конечный период времени.
На практике
Без каких-либо других методов синхронизации, сколько времени потребуется, прежде чем другие потоки смогут это увидеть, используя
x.load(std::memory_order_relaxed);
?
Нет времени. Это обычная запись, она поступает в буфер хранилища, поэтому она будет доступна в кэше L1d быстрее, чем за миг. Но это только при выполнении инструкции по сборке.
Компилятор может переупорядочивать инструкции, но ни один разумный компилятор не переупорядочит атомарные операции по сколь угодно длинным циклам.
Теоретически
В: Теоретически может быть, что такой магазин [
memory_order_relaxed
без (любой последующей) операции выпуска] никогда не достигает другого потока?Марк: Теоретически да,
Вы должны были спросить его, что будет, если снова добавить "ограничитель следующего выпуска". Или с операцией выпуска атомарного хранилища.
Почему бы их не переупорядочить и не отложить на долгое время? (так долго, что на практике это кажется вечностью)
Возможно ли, что значение, записанное в x, останется полностью локальным для потока, учитывая текущее определение модели памяти C/C++, которое дает стандарт?
Если воображаемая и особенно извращенная реализация хотела задержать видимость атомарной операции, почему она должна была делать это только для расслабленных операций? Он вполне мог делать это для всех атомарных операций.
Или никогда не запускайте несколько потоков.
Или запустите несколько потоков так медленно, что вам покажется, что они не работают.