Когда полезен барьер памяти только для компилятора (такой как std :: atomic_signal_fence)?

Понятие забор компилятора часто всплывает, когда я читаю о моделях памяти, барьерах, порядке, атомарности и т. д., но обычно это в контексте также в паре с Забор процессора, как и следовало ожидать.

Иногда, однако, я читал о конструкциях забора, которые только обратиться к компилятору. Примером этого является C ++ 11 std::atomic_signal_fence функция, которая заявляет в cppreference.com:

std :: atomic_signal_fence эквивалентен std :: atomic_thread_fence, за исключением отсутствия процессора
инструкции по упорядочению памяти выдаются. Только переупорядочение
инструкции компилятора подавляются в соответствии с указаниями порядка.

У меня есть пять вопросов, связанных с этой темой:

  1. Как следует из названия std::atomic_signal_fence, является асинхронное прерывание (например, поток, прерываемый ядром для выполнения обработчика сигнала) только случай, в котором компилятор только Забор полезен?

  2. Применима ли его полезность ко всем архитектурам, включая сильно упорядоченная такие как x86?

  3. Может конкретный пример, чтобы продемонстрировать полезность компилятор только забор?

  4. Когда используешь std::atomic_signal_fenceЕсть ли разница между использованием acq_rel а также seq_cst заказ? (Я ожидаю, что это ничего не изменит.)

  5. Этот вопрос может быть охвачен первым вопросом, но мне все равно любопытно спросить конкретно об этом: Когда-либо необходимо использовать заборы с thread_local доступ? (Если бы это было когда-нибудь, я бы ожидал компилятор только заборы, такие как atomic_signal_fence быть инструментом выбора.)

Спасибо.

32

Решение

Чтобы ответить на все 5 вопросов:


1) Забор компилятора (само по себе, без ограждения процессора) полезно только в два ситуации:

  • Укреплять ограничения порядка памяти между одним потоком и асинхронным обработчиком прерываний привязан к тому же потоку (например, обработчик сигнала).

  • Укреплять ограничения порядка памяти между несколькими потоками, когда гарантируется, что каждый поток будет выполняться на том же ядре процессора. Другими словами, приложение будет работать только на одноядерный системы, или приложение принимает специальные меры (через сродство процессора) чтобы каждый поток, который разделяет данные, был связан с одним и тем же ядром.


2) Модель памяти базовой архитектуры, будь то сильно или слабо упорядоченная, не имеет никакого отношения к тому, нужен ли забор компилятора в ситуации.


3) вот псевдокод который демонстрирует использование ограждения компилятора, само по себе, для достаточной синхронизации доступа к памяти между потоком и обработчиком асинхронного сигнала, связанным с тем же потоком:

void async_signal_handler()
{
if ( is_shared_data_initialized )
{
compiler_only_memory_barrier(memory_order::acquire);
... use shared_data ...
}
}

void main()
{
// initialize shared_data ...
shared_data->foo = ...
shared_data->bar = ...
shared_data->baz = ...
// shared_data is now fully initialized and ready to use
compiler_only_memory_barrier(memory_order::release);
is_shared_data_initialized = true;
}

Важная заметка: Этот пример предполагает, что async_signal_handler связан с тем же потоком, который инициализирует shared_data и устанавливает is_initialized флаг, который означает, что приложение является однопоточным, или соответственно устанавливает маски сигналов потоков. В противном случае, ограждение компилятора будет недостаточным, и Забор процессора также будет необходимо.


4) Они должны быть одинаковыми. acq_rel а также seq_cst оба должны приводить к полному (двунаправленному) ограничению компилятора, без каких-либо связанных с забором инструкций ЦП. Концепция «последовательной согласованности» вступает в действие только тогда, когда задействованы несколько ядер и потоков, и atomic_signal_fence относится только к одному потоку исполнения.


5) Нет. (Если, конечно, к локальным данным потока обращаются из асинхронного обработчика сигнала, в этом случае может понадобиться ограждение компилятора.) В противном случае никогда не нужно использовать заборы с локальными данными потока, так как компилятор (и ЦП) разрешены только переупорядочить доступ к памяти способами, которые не изменяют наблюдаемое поведение программы относительно ее последовательность точек с однопоточной точки зрения. И логически можно думать, что локальная статика потока в многопоточной программе такая же, как глобальная статика в однопоточной программе. В обоих случаях данные доступны только из одного потока, что предотвращает возникновение гонки данных.

21

Другие решения

На самом деле есть некоторые непереносимые, но полезные идиомы программирования на С, где ограждения компилятора полезны даже в многоядерном коде (особенно в коде до C11). Типичная ситуация — когда программа делает некоторые доступы, которые обычно бывают нестабильными (потому что они относятся к разделяемым переменным), но вы хотите, чтобы компилятор мог перемещать эти обращения. Если вы знаете, что доступы на целевой платформе являются атомарными (и вы принимаете некоторые другие меры предосторожности), вы можете оставить доступы энергонезависимыми, но содержать перемещение кода с использованием барьеров компилятора.

К счастью, большинство подобных программ устарели с помощью C11 / C ++ 11 расслабленной атомики.

2

По вопросам рекламы [email protected]