Барьеры памяти

ATP>Да я в общем и не утверждаю что не нужны, я как раз интересуюсь в чем здесь тонкие моменты, зачем нужны короче .

Они нужны для обеспечения корректного взаимного упорядочивания обращений к памяти.

Основной паттерн такой — производитель записывает данные в объект, потом записывает указатель на этот объект в разделяемую переменную, потом потребитель считывает указатель на объект из разделяемой переменной, потом считывает данные из объекта. Этот паттерн используется и в очередях производитель-потребитель, и в ленивом многопоточном синглтоне, и в разделяемом хэш-мапе.

В многопоточном окружении *не* гарантируется последовательная консистентность (sequential consistency), т.е. другие потоки могут видеть обращения к памяти не так как они записаны в программе. Причин для неправильного упорядочивания есть 2: первая — переупорядочивания компилятором, вторая — переупорядочивания процессором.

А результате этих переупорядочиваний мы можем получить следующие неприятные ситуации: производитель может записать указатель на объект в разделяемую переменную ещё до записи в него данных и/или потребитель может считать данные из объекта ещё до считывания указателя на объект. В любом случае мы получаем UB.

В алгоритмах многопоточной синхронизации необходимо обеспечивать требуемый взаимный порядок обращений к памяти явно. Для этого служат барьеры памяти (memory barrier, memory fence), в C++0x будет стандартное АПИ для этого, пока же приходится довольствоваться компиляторо-зависимым средствами (в частности _ReadWriteBarrier() подавляет переупорядочивания компилятором вокруг себя).

Вот простой пример (в терминах C++0x):

std::atomic<obj_t*> g_obj; // = 0 // producer obj->data = 123; g_obj.store(obj, std::memory_order_release); // consumerif (obj_t* obj = g_obj.load(std::memory_order_acquire)) assert(obj->data == 123);

std::memory_order_release гарантирует (не формально), что все предыдущие обращения к памяти будут завершены до данного сохранения.

std::memory_order_acquire гарантирует, что все последующие обращения к памяти будут начаты только после завершения данной загрузки.

Недавно я отвечал на подобный вопрос:

http://software.intel.com/ru-ru/forums/showthread.php?t=72142

(там есть примеры барьеров памяти для MSVC, gcc, SUN cc).

В частности вот алгоритм, на котором вы можете наблюдать переупорядочивание обращений процессором на своём многоядерном процессоре х86:

https://en.wikipedia.org/wiki/Peterson%27s_algorithm