Re[4]: Барьеры памяти
От: remark Россия http://www.1024cores.net/
Дата: 13.03.10 14:59
Оценка: 15 (5)
Здравствуйте, AcidTheProgrammer, Вы писали:

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


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.