Подтвердить что ты не робот

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

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

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

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

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

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

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

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

  • При использовании std::atomic_signal_fence существует ли разница между использованием порядка acq_rel и seq_cst? (Я бы ожидал, что это не имеет значения.)

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

Спасибо.

4b9b3361

Ответ 1

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


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

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

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


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


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

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

Ответ 2

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

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