К сожалению, вы очень мало узнаете о том, что поддерживает стандарт, экспериментально с x86_64, потому что x86_64 так хорошо себя ведет. В частности, если вы не укажете _seq_cst :
, если они не пересекают границу строки кэша. И:
- все операции чтения-изменения-записи эффективно seq_cst
За исключением того, что компилятор (также) разрешен изменить порядок _relaxed операций.
Вы упомянули об использовании _relaxed fetch_or ... и если я правильно понимаю, вы можете быть разочарованы, узнав, что это не менее дорого чем seq_cst , и требует инструкции с префиксом LOCK
, несущей все накладные расходы.
Но да, операции _relaxed atomi c неотличимо от обычных операций, что касается порядка. Так что да, они могут быть переупорядочены по другим _relaxed atomi c операциям, а также не-atomi c операциям - компилятором и / или машиной. [Хотя, как отмечалось, на x86_64, а не на машине.]
И, да, где операция освобождения в потоке X синхронизируется с операцией получения в потоке Y, все записи в потоке X, которые упорядочены, до того, как произойдет освобождение - до получения в потоке Y. Таким образом, операция освобождения является сигналом того, что все записи, предшествующие ей в X, «завершены», и когда операция получения видит, что сигнал Y знает, что он синхронизирован и может прочитать что было написано X (до релиза).
Теперь, ключевая вещь, которую нужно понять, это то, что просто сделать магазин _release недостаточно, значение то, что сохранено, должно быть однозначным сигналом для нагрузки _acquire о том, что хранилище произошло. В противном случае, как загрузка может сказать?
Обычно пара _release / _acquire , как эта, используется для синхронизации доступа к некоторому набору данных. Как только эти данные «готовы», магазин _release сообщает об этом. Любая нагрузка _acquire , которая видит сигнал (или все нагрузки _acquire , которые видят сигнал), знает, что данные «готовы», и они могут их прочитать. Конечно, любые записи в данные, которые идут после хранилища _release , могут (в зависимости от времени) также просматриваться нагрузкой (ами) _acquire . Здесь я пытаюсь сказать, что может потребоваться еще один сигнал для дальнейших изменений данных.
Ваша маленькая тестовая программа:
инициализирует tsync
в 0
в записывающем устройстве: после tat[i].store(j, memory_order_relaxed)
, tsync.store(0, memory_order_release)
, поэтому значение tsync
не изменяется!
в считывателе: tsync.load(memory_order_acquire)
перед выполнением tat[i].load(memory_order_relaxed)
и игнорирует значение, считанное с tsync
Я здесь, чтобы сказать вам, что пары _release / _acquire не синхронизируются - все эти хранилища / загрузка также могут быть _relaxed . [Я думаю, что ваш тест «пройдет», если автору удастся опередить читателя. Потому что на x86-64 все записи выполняются в порядке следования, как и все чтения.]
Для того, чтобы это было тестом _release / _acquire семантика, я предложить:
инициализирует tsync
в 0 и tat[]
во все нули.
в модуле записи: запустите j = 1..numTries
после всех tat[i].store(j, memory_order_relaxed)
, запишите tsync.store(j, memory_order_release)
, это сигнализирует о завершении передачи и что все tat[]
теперь j
.
в считывателе: сделайте j = tsync.load(memory_order_acquire)
проход через tat[]
должен найти j <= tat[i].load(memory_order_relaxed)
и после прохода j == numTries
сигнализирует, что писатель закончил.
где сигнал, отправленный автором, состоит в том, что он только что завершил запись j
и будет продолжать с j+1
, если j == numTries
. Но это не гарантирует порядок, в котором написаны tat[]
.
Если вы хотели, чтобы писатель останавливался после каждого прохода и ждал, пока читатель его увидит, и сигнал тот же - тогда вам нужен другой сигнал, и потоки должны ждать соответствующего сигнала «вы можете продолжить».