Я работаю над заменой системы распределения для «стабильных указателей» в системе времени выполнения ghc, и я подхожу к пределам моего понимания параллельного программирования.
Предположим, что переменная содержит 0. Поток A использует __atomic_fetch_and_add для увеличения переменной и каким-то образом уведомляет поток B. В ответ поток B использует __atomic_fetch_and_add для уменьшения значения той же переменной, возвращая ее к 0. Таким образом, кажется, что переменная должна перейти от 0 до 1 и обратно. Гарантируется ли, что другой поток C не увидит добавления, выполняемые в обратном порядке, от 0 до -1 и обратно?
@ curiousguy12, не могли бы вы процитировать надежный источник, чтобы обновить этот комментарий до ответа?





Я просто перечитал этот вопрос, добавив некоторые дополнительные разъяснения, и понял, что предполагал C11, в то время как ваш вопрос, похоже, использует встроенные компоненты компилятора. С этой точки зрения, если все ваше использование memorder - это __ATOMIC_SEQ_CST, нет случая, в котором вы можете наблюдать значение -1 по тем же причинам, которые я подробно описываю ниже (из C11).
TL; DR: Это зависит от обстоятельств, но вам придется действительно выстрелить себе в ногу, чтобы такое поведение не было гарантировано. Ниже следует объяснение того, почему это мог происходит, как это могло произойти, и почему вы вряд ли столкнетесь с этим.
Гарантируется, что атомарные операции имеют глобальный порядок, но этот глобальный общий порядок не определен. Из черновика C11, §5.1.2.4p7:
All modifications to a particular atomic object M occur in some particular total order, called the modification order of M.
Благодаря этому определению модификаций M возможно, что общий порядок, наблюдаемый каким-либо другим потоком, будет A / B, но B / A также разрешен. Это действительно привело бы к тому, что внешний наблюдатель заметил бы переход значения между -1 и 0 (при условии, что атомный тип со знаком).
Чтобы справиться с этим, стандарт определяет операции синхронизации (из параграфа 5 того же раздела):
A synchronization operation on one or more memory locations is either an acquire operation, a release operation, both an acquire and release operation, or a consume operation.
Позже есть несколько утомительных для чтения определений компоновки этих операций, чтобы ввести зависимости, которые в конечном итоге приводят к упорядочиванию «происходит до». Я их опущу; §5.1.2.4p14-22 описывает наблюдаемость побочных эффектов для некоторого объекта и то, как зависимости влияют на это; В §7.17.3 описывается API для управления этими зависимостями.
Не обсуждая эти разделы, можно надеяться, что достаточно сказать, что они позволяют наблюдателю видеть описанный «противоположный порядок». Вы можете попасть в эту ситуацию, если используете atomic_fetch_add_explicit с аргументом memory_order_relaxed, а ваша нагрузка реализована как atomic_load_explicit с такими же ослабленными требованиями к упорядочиванию памяти. В этой ситуации связь «происходит до» не определена, и системе разрешается разрешить потоку C наблюдать за изменениями в любом порядке.
Маловероятно, что вы действительно поступили бы так. Во-первых, это намного больше. Во-вторых, именование и использование API действительно предполагает, что вы должны знать, что делаете, если хотите его использовать. Это то, что я имею в виду, когда говорю, что вам действительно придется выстрелить себе в ногу: по умолчанию вам не рекомендуется делать такие вещи.
Если бы вы реализовали это исключительно с atomic_fetch_add, atomic_fetch_sub и atomic_load (как вы, вероятно, сделали бы), все будет в порядке; стандарт в §7.17.1p5 гласит:
The functions not ending in _explicit have the same semantics as the corresponding _explicit function with memory_order_seq_cst for the memory_order argument.
Стандарт гарантирует, что этот порядок будет нести зависимости данных, так что запись из потока A будет считаться «происходящей до» записи из потока B. Таким образом, наблюдатель C со своими собственными согласованными требованиями к упорядочиванию памяти гарантированно видит операции. чередовать в указанном порядке.
При этом все сказано: если вы можете использовать C11, просто используйте ++, -- и =, и все будет в порядке. Согласно §6.5.16.2p3, операции += и -= с атомарными типами определены так, как если бы они использовали хранилище с memory_order_seq_cst. Согласно §6.5.3p2 операторы ++ и -- аналогичны эквивалентным выражениям x+=1 и x-=1. Простое присвоение (§6.5.16.2) указывает, что LHS или RHS могут быть атомарными типами, но не определяет порядок памяти. Йенс Густедт говорит, что операции с объектами, отвечающими требованиям _Atomic, гарантированно имеют последовательную согласованность. Я могу догадаться об этом только по сноске 113, а сноски не являются нормативными. Но я не думаю, что это имеет значение: если все записи согласованы, при любом чтении должно соблюдаться действительное предыдущее состояние из этого общего порядка, которое никогда не содержит -1.
Да, это гарантировано. Если C выполняет случайные атомные нагрузки, он может видеть изменение переменной только в том же порядке.