Home‎ > ‎In Russian‎ > ‎

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

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);

// consumer
if (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:
http://software.intel.com/ru-ru/forums/showpost.php?p=110773

Comments