Обычный шаблон для использования барьера памяти совпадает с тем, что вы бы поместили в реализацию критического раздела, но разбит на пары для производителя и потребителя. Например, ваша реализация критического раздела обычно имеет вид:
while (!pShared->lock.testAndSet_Acquire()) ;
// (this loop should include all the normal critical section stuff like
// spin, waste,
// pause() instructions, and last-resort-give-up-and-blocking on a resource
// until the lock is made available.)
// Access to shared memory.
pShared->foo = 1
v = pShared-> goo
pShared->lock.clear_Release()
Приобретенный выше барьер памяти гарантирует, что любые нагрузки (pShared-> goo), которые могли быть запущены до успешного изменения блокировки, будут перезапущены при необходимости.
Барьер памяти освобождения гарантирует, что загрузка из goo в (локальную, скажем) переменную v завершена до того, как будет очищено слово блокировки, защищающее разделяемую память.
У вас есть похожая схема в типичном сценарии атомного флага производителя и потребителя (по вашему образцу трудно определить, что вы делаете, но должны проиллюстрировать идею).
Предположим, ваш производитель использовал атомарную переменную, чтобы указать, что какое-то другое состояние готово к использованию. Вы хотите что-то вроде этого:
pShared->goo = 14
pShared->atomic.setBit_Release()
Без барьера «записи» здесь, в производителе, у вас нет гарантии, что аппаратное обеспечение не попадет в атомарное хранилище до того, как хранилище goo пройдет через очереди хранилища ЦП, и вверх по иерархии памяти, где это видно (даже если у вас есть механизм, который гарантирует, что компилятор упорядочивает вещи так, как вы хотите).
у потребителя
if ( pShared->atomic.compareAndSwap_Acquire(1,1) )
{
v = pShared->goo
}
Без барьера «чтения» здесь вы не узнаете, что аппаратное обеспечение не пошло и не принесло вам липучки до завершения атомарного доступа. Атомарный (то есть: память, которой манипулируют с помощью функций Interlocked, выполняющих такие вещи, как блокировка cmpxchg), является только «атомарной» по отношению к себе, а не к другой памяти.
Теперь осталось упомянуть, что барьерные конструкции крайне непереносимы. Ваш компилятор, вероятно, предоставляет варианты _acquire и _release для большинства методов атомарных манипуляций, и это те способы, которыми вы могли бы их использовать. В зависимости от используемой вами платформы (например, ia32), они вполне могут быть именно тем, что вы получите без суффиксов _acquire () или _release (). Платформы, где это имеет значение, ia64 (фактически мертвые, за исключением HP, где он все еще слегка дергается) и powerpc. У ia64 были модификаторы инструкций .acq и .rel в большинстве инструкций загрузки и хранения (включая атомарные, такие как cmpxchg). PowerPC имеет отдельные инструкции для этого (isync и lwsync дают вам барьеры для чтения и записи соответственно).
Теперь. Сказав все это. У вас действительно есть веская причина пойти по этому пути? Делать все это правильно может быть очень сложно. Будьте готовы к большому количеству сомнений и небезопасности в обзорах кода и убедитесь, что у вас есть много тестов с высоким параллелизмом со всеми видами случайных сценариев синхронизации. Используйте критический раздел, если у вас нет очень веской причины избегать его, и не пишите этот критический раздел самостоятельно.