Страниц: [1]
  Печать  
Автор Тема: Вопрос о C++11 и синхронизации.  (Прочитано 7706 раз)
vshemm
Sr. Member
****
Offline Offline

Сообщений: 317


Просмотр профиля
« : Октября 02, 2013, 11:13:09 pm »

Правда ли, что atomic_thread_fence() гарантирует только аппаратные барьеры (для SMP) в зависимости от модели памяти, но не гарантирует барьеры компилятора? А atomic_signal_fence() - только реентерабельность для текущей нити (синхронизацию нить-сигнал), т.е. в максимуме - барьер компилятора.

Таким образом, если мы хотим получить и хардварный барьер, и компиляторный, мы должны юзать обе функции.

oder, WTF? Smiley
Записан
oder
Гость
« Ответ #1 : Октября 03, 2013, 02:40:02 am »

Нет.
atomic_thread_fence() устанавливает нормальный барьер для нитей (и поцессорный и компиляторный), а atomic_signal_fence() устанавливает барьер лишь для обработчиков сигналов, которые исполняются в той же нити (только компиляторный ибо процессорный для одной и той же нити не нужен). Это понятно из описания к atomic_signal_fence():

Цитировать
extern "C" void atomic_signal_fence(memory_order order) noexcept;

Effects: equivalent to atomic_thread_fence(order), except that the resulting ordering constraints
are established only between a thread and a signal handler executed in the same thread.

Note: atomic_signal_fence can be used to specify the order in which actions performed by the thread
become visible to the signal handler.

Note: compiler optimizations and reorderings of loads and stores are inhibited in the same way as with
atomic_thread_fence, but the hardware fence instructions that atomic_thread_fence would have
inserted are not emitted.
Записан
vshemm
Sr. Member
****
Offline Offline

Сообщений: 317


Просмотр профиля
« Ответ #2 : Октября 04, 2013, 06:07:31 am »

Хех, строго говоря, в описании дается ссылка на atomic_thread_fence(), а там про компиляторные
барьеры ни слова. Хотя из общего предназначения данных функций они там должны быть. К тому же,
в различных библиотеках времен с++0х реализация включает компиляторные барьеры.

Я тоже так думал и все было прекрасно, пока не столкнулся с обратным. Код примерно такой:
Код:
char *p;
int i;
...
i = 42;
std::atomic_thread_fence(std::memory_order_release);
p = something;

Компилировалось все gcc 4.8.1 под х86 (-march=core2 и выше) с неагрессивными оптимизациями. Так как
эти CPU strong-ordered и не переупорядочивают записи, то никаких хардварных барьеров ставить не надо.
Однако, компиляторный барьер тоже не ставится, и в ассемблерном выхлопе операции меняются местами,
что приводит к печальным последствиям (парный барьер есть, работает для любых моделей, кроме
std::memory_order_seq_cst).

Ситуацию исправляет atomic_signal_fence()/asm volatile("" ::: "memory")/заворачивание переменных в
std::atomic или добавление к ним volatile, но вопрос не в этом.

К сожалению, код пока показать не могу, а минимально "работающий" пример с подобными оптимизациями
создать пока не удалось, компилятор заставить непросто Smiley

Такие дела.
Записан
oder
Гость
« Ответ #3 : Октября 04, 2013, 12:05:28 pm »

Ну бывают ошибки и в библиотеке и в компиляторе: технология-то пока ещё относительно новая. Если функция реализована макроподстановкой или темплейтами в библиотеке - можно и самому исправить и написать уведомление об ошибке разработчикам с явным указанием места. Если исходников нет - надо просто описывать ситуацию и пусть они там внутри смотрят.

Я, пока что, на 4.7.2 сижу - там обе функци реализованы как перевызов __atomic_thread_fence()
Записан
oder
Гость
« Ответ #4 : Октября 04, 2013, 01:42:23 pm »

Ещё одна идея, которая на первый взгляд может показаться бредовой, но, боюсь, окажется правдой.

Вполне вероятно, что atomic_thread_fence() и atomic_signal_fence() обязаны действовать лишь относительно операций с atomic переменными и не обязаны иметь влияние на остальные. Тоесть, они являются средством локального усиления барьера в местах, где самого разделения, вытекающего из моделей упорядочения доступа к atomic переменным, недостаточно.

Там в описании атомиков эта их (атомиков) изолированность от всего прочего мира постоянно "витает в воздухе".
« Последнее редактирование: Октября 04, 2013, 06:58:22 pm от oder » Записан
vshemm
Sr. Member
****
Offline Offline

Сообщений: 317


Просмотр профиля
« Ответ #5 : Октября 05, 2013, 07:36:29 pm »

В 4.8.1 тоже через инлайновый __atomic_thread_fence(), а это интринсик. Но прежде чем постить
багрепорты, надо разобраться, как оно должно работать Smiley Ну или хотя бы иметь steps to reproduce.

Ещё одна идея, которая на первый взгляд может показаться бредовой, но, боюсь, окажется правдой.

Вполне вероятно, что atomic_thread_fence() и atomic_signal_fence() обязаны действовать лишь относительно операций с atomic переменными и не обязаны иметь влияние на остальные. Тоесть, они являются средством локального усиления барьера в местах, где самого разделения, вытекающего из моделей упорядочения доступа к atomic переменным, недостаточно.

Там в описании атомиков эта их (атомиков) изолированность от всего прочего мира постоянно "витает в воздухе".

Почему бредовая, я к такой же мысли пришел. В свежайшем драфте стандарта (п29.8 ) все фенсы описываются
через модификацию операций с атомиками, а уже операции с общими атомиками влияют на операции с
обычными переменными.

Меня смущало вот это описание http://en.cppreference.com/w/cpp/atomic/atomic_thread_fence:
Цитировать
Establishes memory synchronization ordering of non-atomic and relaxed atomic accesses, as instructed by order, without an associated atomic operation. For example, all non-atomic and relaxed atomic stores that happen before a std::memory_order_release fence in thread A will be synchronized with non-atomic and relaxed atomic loads from the same locations made in thread B after an std::memory_order_acquire fence.
Все это так, только забыли указать необходимость существования операций с атомиками, причем с нужной
стороны барьера.

Вообще, модель памяти с++11 не включает в себя такие понятия, как барьер компилятора (по задумке),
поэтому все фенсы и атомики просто обеспечивают данную модель, не более. Однозначно отобразить ее на
множество операций с хардварными/компиляторными барьерами невозможно без контекста. Отсюда и недопонимание...

Так что аккуратнее Smiley
Записан
vshemm
Sr. Member
****
Offline Offline

Сообщений: 317


Просмотр профиля
« Ответ #6 : Октября 05, 2013, 08:02:30 pm »

Примеры:

Код:
int i = 0;
atomic<char *> p(nullptr);

thread 1:                                       thread 2:
i = 42;                                         char *pt = p.load(memory_order_relaxed);
atomic_thread_fence(memory_order_release);      atomic_thread_fence(memory_order_acquire);
p.store(something, memory_order_relaxed);       int j = i;
                                                if (pt) assert(j == 42); // never fires

Код:
int i = 0;
atomic<char *> p(nullptr);

thread 1:                                       thread 2:
i = 42;                                         char *pt = p.load(memory_order_acquire);
p.store(something, memory_order_release);       int j = i;
                                                if (pt) assert(j == 42); // never fires

Код:
int i = 0;
char *p(nullptr);

thread 1:                                       thread 2:
i = 42;                                         char *pt = p;
atomic_thread_fence(memory_order_release);      atomic_thread_fence(memory_order_acquire);
p = something;                                  int j = i;
                                                if (pt) assert(j == 42); // may fire
Записан
Страниц: [1]
  Печать  
 
Перейти в: