Барьеры памяти против блокированных операций - PullRequest
8 голосов
/ 22 июля 2010

Я пытаюсь улучшить мое понимание барьеров памяти. Предположим, у нас слабая модель памяти, и мы адаптируем алгоритм Деккера . Можно ли заставить его работать правильно при слабой модели памяти, добавив барьеры памяти?

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

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

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

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

Ответы [ 3 ]

5 голосов
/ 27 июля 2010

Вы правы в том, что барьер памяти не может гарантировать, что чтение видит актуальные значения.Что он делает, так это устанавливает порядок между операциями, как в одном потоке, так и между потоками.

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

// initially x=y=z=flag=0

// thread A
x=1;
y=2;
z=3;
release_barrier();
flag=1;

// thread B
while(flag==0) ; // loop until flag is 1
acquire_barrier();
assert(x==1);  // asserts will not fire
assert(y==2);
assert(z==3);

Конечно, вам нужноубедитесь, что загрузка и сохранение в flag являются атомарными (что простые инструкции загрузки и сохранения находятся на большинстве распространенных процессоров, при условии, что переменные соответствующим образом выровнены).Без цикла while в потоке B поток B вполне может прочитать устаревшее значение (0) для flag, и, таким образом, вы не можете гарантировать любое из значений, считанных для других переменных.

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

Вот пример реализации на C ++ (с использованием атомарных переменных C ++ 0x):

std::atomic<bool> flag0(false),flag1(false);
std::atomic<int> turn(0);

void p0()
{
    flag0.store(true,std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_seq_cst);

    while (flag1.load(std::memory_order_relaxed))
    {
        if (turn.load(std::memory_order_relaxed) != 0)
        {
            flag0.store(false,std::memory_order_relaxed);
            while (turn.load(std::memory_order_relaxed) != 0)
            {
            }
            flag0.store(true,std::memory_order_relaxed);
            std::atomic_thread_fence(std::memory_order_seq_cst);
        }
    }
    std::atomic_thread_fence(std::memory_order_acquire);

    // critical section


    turn.store(1,std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_release);
    flag0.store(false,std::memory_order_relaxed);
}

void p1()
{
    flag1.store(true,std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_seq_cst);

    while (flag0.load(std::memory_order_relaxed))
    {
        if (turn.load(std::memory_order_relaxed) != 1)
        {
            flag1.store(false,std::memory_order_relaxed);
            while (turn.load(std::memory_order_relaxed) != 1)
            {
            }
            flag1.store(true,std::memory_order_relaxed);
            std::atomic_thread_fence(std::memory_order_seq_cst);
        }
    }
    std::atomic_thread_fence(std::memory_order_acquire);

    // critical section


    turn.store(0,std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_release);
    flag1.store(false,std::memory_order_relaxed);
}

Полный анализ см. в моей записи блога по адресу http://www.justsoftwaresolutions.co.uk/threading/implementing_dekkers_algorithm_with_fences.html

1 голос
/ 25 июля 2010

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

http://www.cs.nmsu.edu/~pfeiffer/classes/573/notes/consistency.html

Даже на процессоре с моделью слабой согласованности можно ожидать когерентности кэша.Я думаю, что в случае сбоев происходит поведение буферов хранилища и предполагаемых операций чтения, а также доступных операций сброса сохраненных записей и аннулирования предположительных операций чтения.Если нет ограничения загрузки, которое может сделать недействительными предполагаемые чтения, или нет ограничения записи, которое очищает буфер хранилища, в дополнение к невозможности реализовать Dekker, вы не сможете реализовать мьютекс!

Так вот мое требование.Если у вас есть доступный барьер записи и барьер чтения, а кэш-память согласована между процессорами, то вы можете тривиально согласовать весь код последовательно, сбрасывая записи (хранить забор) после каждой инструкции и сбрасывая предположения (забор чтения) перед каждыминструкция.Поэтому я утверждаю, что вам не нужны атомы для того, о чем вы говорите, и что вы можете делать то, что вам нужно, только с Деккером.Конечно, вы не захотите.

Кстати, Corensic, компания, в которой я работаю, пишет классные инструменты для устранения проблем с параллелизмом.Проверить http://www.corensic.com.

0 голосов
/ 23 июля 2010

Некоторые барьеры (такие как powerpc isync и нагрузка .acq на ia64) также влияют на последующие нагрузки.То есть: если нагрузка была доступна до isync из-за предварительной выборки, она должна быть отброшена.При правильном использовании, возможно, этого достаточно, чтобы алгоритм Деккера работал на слабой модели памяти.

У вас также работает логика аннулирования кэша.Если вы знаете, что ваша нагрузка является текущей из-за чего-то вроде isync, и что кэшированная версия данных становится недействительной, если ее затрагивает другой процессор, достаточно ли этого?цели тупые.Вы захотите использовать атомарные аппаратные интерфейсы и барьеры памяти для любого реального приложения, поэтому сосредоточение внимания на том, как исправить Dekker'ом с помощью атомных компонентов, просто не кажется мне стоящим;)

...