При замене memory_order_acquire на memory_order_consume в загрузке указателя какие данные гарантированно видит поток?
memory_order_consume гарантирует видимость только тех операций, которые зависят от загруженного значения. Если атомарная загрузка указателя получает значение из release-операции, то чтение объекта через этот указатель может увидеть данные, подготовленные до публикации. Независимые обращения к другим объектам такой гарантии не получают; для общего случая нужен memory_order_acquire.
memory_order_consume появился для архитектур, где адресная зависимость между операциями может обеспечивать нужный порядок без более дорогого acquire-барьера. Типичный сценарий — публикация указателя на полностью инициализированный объект и последующее чтение объекта через загруженный указатель.
На практике отслеживание зависимостей сложно для компиляторов. Поэтому реализации часто обрабатывают consume-конструкции как acquire, не предоставляя ожидаемого выигрыша производительности. Это делает consume редким выбором в прикладном коде.
Пусть один поток инициализирует объект, а затем публикует его адрес release-операцией. Другой поток загружает адрес и читает объект. Ошибка возникает, если разработчик считает consume эквивалентом acquire для любых последующих операций или заменяет его на relaxed, не учитывая зависимость.
В результате независимые данные могут читаться без требуемой гарантии видимости. Если же доступ к объекту происходит через корректную зависимость от загруженного указателя, consume предоставляет более узкую гарантию.
Загрузка consume должна получить значение, которое было опубликовано release-операцией или находится в соответствующей release-последовательности. Операции, адрес или значение которых вычисляются с зависимостью от результата загрузки, получают порядок относительно предшествующих публикации записей.
Например, разыменование загруженного указателя является зависимым чтением:
Если published получил указатель через release-публикацию после инициализации Node, чтение p->value связано с результатом загрузки. Но чтение отдельной глобальной переменной, обращение к которой не зависит от p, такой гарантии не получает.
Acquire проще для понимания и распространяет гарантию на все последующие операции потока, если загрузка действительно синхронизировалась с release. Relaxed не устанавливает необходимого порядка публикации объекта. Поэтому consume имеет смысл только при точном контроле зависимостей и понимании используемой реализации; обычно безопаснее и практичнее выбрать acquire.
В lock-free структуре поток-производитель создаёт узел, заполняет его поля и публикует указатель. Поток-потребитель читает указатель и затем поля узла. Рассматривались три варианта: relaxed был отклонён из-за отсутствия гарантии публикации, consume теоретически минимизировал бы требования к порядку, а acquire давал ясную и переносимую семантику.
Был выбран acquire. Возможная экономия от consume не была подтверждена измерениями, а риск нарушения зависимости при последующем рефакторинге оказался существеннее. В результате код сохранил корректность при изменении реализации и не потребовал от команды отслеживать тонкости dependency-ordered-before.
Нет. Нужна зависимость, влияющая на вычисление результата операции или адрес обращения. Сам факт того, что указатель был прочитан ранее или использован в независимой проверке, не превращает любое последующее чтение в зависимое.
Технически поведение текущей реализации может оказаться эквивалентным acquire, но это не делает замену хорошим универсальным решением. Семантика программы должна опираться на стандартную гарантию, а не на текущее решение компилятора. Изменение компилятора, оптимизаций или архитектуры может выявить ошибочное предположение о независимых обращениях.
Простое копирование значения обычно сохраняет зависимость, если дальнейшее обращение действительно вычисляется через это значение. Однако преобразования, устраняющие зависимость, или использование указателя в заранее вычисленном независимом адресе могут лишить последующую операцию требуемой гарантии. Поэтому в сложном коде acquire предпочтительнее: он не требует доказывать сохранение зависимости на каждом пути.