Барьеры памяти: как гарантировать, что записи инициализации видны рабочим потокам?

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

int setup, sheep;

void SetupSheep():    // Run once
CPU 1: setup = 0;
... much later
CPU 1: sheep = 9;
CPU 1: std::atomic_thread_fence(std::memory_order_release);
CPU 1: setup = 1;

Запустить потом (не одновременно), много, много раз:

void ManipulateSheep():
CPU 2: int mySetup = setup;
CPU 2: std::atomic_thread_fence(std::memory_order_acquire);
CPU 2: // Use sheep...

На CPU 2, если mySetup это 1, sheep тогда гарантированно будет 9 — но как мы можем гарантировать, что mySetup не 0?

Пока что все, о чем я могу думать, это крутить-ждать на CPU 2, пока setup 1. Но это кажется довольно уродливым, учитывая, что спин-ожидание будет ждать только в первый раз ManipulateSheep() назывался. Конечно, должен быть лучший способ?

Обратите внимание, что есть также симметричная проблема с кодом деинициализации: допустим, вы пишете структуру данных без блокировки, которая выделяет память в течение всего срока ее службы. В деструкторе (при условии, что все потоки закончили вызывать методы) вы хотите освободить всю память, а это означает, что вам нужен процессор, на котором запущен деструктор, чтобы иметь самые последние значения переменных. В этом сценарии даже невозможно вращаться в ожидании, так как деструктор не сможет узнать, каково было «последнее» состояние, чтобы проверить его.

редактировать: Я предполагаю, что я спрашиваю: есть ли способ сказать «Подождите, пока все мои хранилища распространятся на другие процессоры» (для инициализации) и «Подождите, пока все хранилища распространятся на мой процессор» (для деинициализации)?

4

Решение

Оказывается, что #StoreLoad это точно правильный барьер для этой ситуации. Как объяснил просто Джефф Прешинг:

Барьер StoreLoad гарантирует, что все хранилища, выполненные до барьера, будут видны другим процессорам, и что все нагрузки, выполненные после барьера, получат самое последнее значение, которое видно во время барьера.

В C ++ 11 std::atomic_thread_fence(std::memory_order_seq_cst) по-видимому, действует как #StoreLoad барьер (как и остальные три: #StoreStore, #LoadLoad, а также #LoadStore). Увидеть этот проект C ++ 11.

Примечание: на x86 mfence инструкция действует как #StoreLoad; как правило, это может быть испущено с _mm_fence() встроенный компилятор, если это необходимо.

Таким образом, шаблон для кода без блокировки может быть:

Initialize:
CPU 1: setupStuff();
CPU 1: std::atomic_thread_fence(std::memory_order_seq_cst);

Run parallel stuff

Uninitialize:
CPU 2: std::atomic_thread_fence(std::memory_order_seq_cst);
CPU 2: teardownStuff();
1

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

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

Тем не менее, барьеры, которые вы показали в вашем примере, по крайней мере, достаточно, чтобы ManipulateSheep могу сказать, готовы ли овцы еще.

(Баа.)

0

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