memory_order_acquire
имеет смысл только для операций, которые читают значение, а memory_order_release
имеет смысл только для операций, которые записывают значение. Поскольку операции чтения-изменения-записи позволяют выполнять чтение и запись, можно объединять эти порядки памяти, но это не всегда необходимо.
m_event.m_state.compare_exchange_weak
использует memory_order_release
для записи нового значения, поскольку оно пытается заменить значение, которое ранее было прочитано, с помощью memory_order_acquire:
// load initial value using memory_order_acquire
void* oldValue = m_event.m_state.load(std::memory_order_acquire);
do {
...
} while (!m_event.m_state.compare_exchange_weak(oldValue, this,
std::memory_order_release,
// in case of failure, load new value using memory_order_acquire
std::memory_order_acquire));
IMHO в этом случае даже не нужно вообще использовать memory_order_acquire, поскольку oldValue никогда не разыменовывается, а только сохраняется как следующий указатель, т. е. было бы идеально найти заменить эти два memory_order_acquire на memory_order_relaxed.
В async_manual_reset_event::set()
ситуация отличается:
void* oldValue = m_state.exchange(this, std::memory_order_acq_rel);
if (oldValue != this)
{
auto* waiters = static_cast<awaiter*>(oldValue);
while (waiters != nullptr)
{
// we are de-referencing the pointer read from m_state!
auto* next = waiters->m_next;
waiters->m_awaitingCoroutine.resume();
waiters = next;
}
Так как мы снимаем ссылки с указателя, мы при чтении m_state
мы должны убедиться, что эти чтения происходят после записи в эти объекты-официанты. Это обеспечивается с помощью отношения синхронизации с m_state
. Writer добавляется с помощью ранее обсужденного Compare_exchange с использованием memory_order_release
. Часть receive обмена синхронизируется с release-compare_exchange (и фактически всеми предыдущими release-compare_exchange, которые являются частью последовательности релизов), обеспечивая тем самым необходимое отношение «до и после».
Если честно, Я не уверен, зачем этому обмену нужна часть релиза. Я думаю, что автор, возможно, хотел быть на «безопасной стороне», поскольку некоторые другие операции также сильнее, чем необходимо (я уже упоминал, что await_suspend
не требует memory_order_acquire, и то же самое относится к is_set
и reset
).
Для вашей реализации блокировки это очень просто - когда вы хотите получить блокировку (try_lock_shared
/ try_lock
), используйте memory_order_acquire
только для операции сравнения-обмена. Для снятия блокировки необходимо использовать memory_order_release
.
Аргумент также довольно прост: вы должны убедиться, что после того, как вы получили блокировку, любые изменения, ранее сделанные в данных, защищенных блокировкой, были видны текущий владелец, то есть вы должны убедиться, что эти изменения произошли до операций, которые вы собираетесь выполнить после получения блокировки . Это достигается путем установления отношения синхронизации с try_lock
(acqu-CAS) и предыдущим unlock
(release-store).
При попытке спорить о правильности реализации на основе Семантика модели памяти C ++. Обычно я делаю это следующим образом:
- идентифицирует необходимые отношения до (например, для вашей блокировки)
- , чтобы убедиться, что эти отношения происходят до. установлены правильно на всех путях кода
И я всегда комментирую операции atomi c, чтобы документировать, как устанавливаются эти отношения (т. е. какие другие операции задействованы). Например:
// (1) - this acquire-load synchronizes-with the release-CAS (11)
auto n = head.load(std::memory_order_acquire);
// (8) - this acquire-load synchronizes-with the release-CAS (11)
h.acquire(head, std::memory_order_acquire);
// (11) - this release-CAS synchronizes-with the acquire-load (1, 8)
if (head.compare_exchange_weak(expected, next, std::memory_order_release, std::memory_order_relaxed))
(полный код см. https://github.com/mpoeter/xenium/blob/master/xenium/michael_scott_queue.hpp)
Для получения более подробной информации о модели памяти C ++ я могу порекомендовать этот документ, который у меня есть в соавторстве: Модели памяти для программистов на C / C ++