Заборы в C ++ 0x, гарантирует только на атомиках или памяти вообще - PullRequest
12 голосов
/ 05 апреля 2011

Черновик C ++ 0x имеет понятие ограждений, которое кажется очень отличным от понятия ограждений на уровне процессора / чипа или того, что ребята из ядра Linux ожидают от ограждений, Вопрос в том, действительно ли проект подразумевает крайне ограниченную модель, или формулировка просто плохая, и на самом деле она подразумевает настоящие ограждения.

Например, под 29,8 Заборами в нем указаны такие вещи, как:

Забор А синхронизируется с приобрести забор B, если существуют атомные операции X и Y, обе работают на некоторый атомный объект М, такой, что А является секвенируется перед X, X изменяет M, Y последовательность перед B, и Y читает значение написано X или значение написано любой стороной эффекта в гипотетическом последовательность выпуска X будет возглавлять, если это были операции освобождения.

Используются эти термины atomic operations и atomic object. Есть такие атомарные операции и методы, определенные в проекте, но означает ли это только те? A разделительный забор звучит как магазинный забор . хранить забор , который не гарантирует запись всех данных до забора, практически бесполезен. Похоже на загрузочный (полный) забор и полный забор.

Итак, являются ли заборы / ограждения в собственных заборах C ++ 0x и формулировки просто невероятно плохими, или они чрезвычайно ограничены / бесполезны, как описано?


С точки зрения C ++, скажем, у меня есть этот существующий код (при условии, что заборы доступны как конструкции высокого уровня прямо сейчас - вместо того, чтобы, скажем, использовать __sync_synchronize в GCC):

Thread A:
b = 9;
store_fence();
a = 5;

Thread B:
if( a == 5 )
{
  load_fence();
  c = b;
}

Предположим, что a, b, c имеют размер, чтобы иметь атомную копию на платформе. Вышеуказанное означает, что c будет назначено только 9. Обратите внимание, что нам все равно, когда поток B видит a==5, просто когда он это делает, он также видит b==9.

Какой код в C ++ 0x гарантирует такое же отношение?


ОТВЕТ : Если вы прочитаете мой выбранный ответ и все комментарии, вы получите суть ситуации. C ++ 0x, по-видимому, заставляет вас использовать атомарный элемент с заборами, тогда как обычный аппаратный забор не имеет этого требования. Во многих случаях это все еще можно использовать для замены параллельных алгоритмов, если sizeof(atomic<T>) == sizeof(T) и atomic<T>.is_lock_free() == true.

К сожалению, is_lock_free не является контрагентом. Это позволило бы использовать его в static_assert. Дегенерация atomic<T> до использования блокировок, как правило, плохая идея: атомарные алгоритмы, использующие мьютексы, будут иметь ужасные проблемы конкуренции по сравнению с алгоритмом, разработанным мьютексами.

Ответы [ 2 ]

15 голосов
/ 05 апреля 2011

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

std::atomic<bool> ready(false);
int data=0;

void thread_1()
{
    data=42;
    std::atomic_thread_fence(std::memory_order_release);
    ready.store(true,std::memory_order_relaxed);
}

void thread_2()
{
    if(ready.load(std::memory_order_relaxed))
    {
        std::atomic_thread_fence(std::memory_order_acquire);
        std::cout<<"data="<<data<<std::endl;
    }
}

Если чтение thread_2ready будет true, тогда заборы обеспечат безопасное считывание data, а на выходе будет data=42.Если для ready указано значение false, то вы не можете гарантировать, что thread_1 произвёл соответствующее ограждение, поэтому ограждение в потоке 2 все равно не обеспечит необходимые гарантии упорядочения - если if in thread_2 был опущен, доступ к data был бы гонкой данных и неопределенным поведением, даже с забором.

Уточнение: A std::atomic_thread_fence(std::memory_order_release) обычно эквивалентен забору магазина и, вероятно, будет реализованв качестве таких.Однако одно ограждение на одном процессоре не гарантирует какого-либо упорядочения памяти: вам нужно соответствующее ограждение на втором процессоре, И , вы должны знать, что когда было выполнено ограждение получения, эффекты ограждения освобождения быливиден этот второй процессор.Очевидно, что если ЦП A выдает ограничение получения, а затем через 5 секунд ЦП B выдает ограничение выпуска, то это ограничение выпуска не может синхронизироваться с ограничением получения.Если у вас нет каких-либо средств проверки того, был ли установлен запрет на другом процессоре, код на процессоре A не может сказать, выдал ли он свой барьер до или после барьера на процессоре B.

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

Конечно, можно использовать более сильный механизм, такой как мьютекс, но это сделает отдельный забор бессмысленным, так как мьютекс обеспечит забор.

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

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

2 голосов
/ 05 апреля 2011

Насколько я понимаю, это правильные заборы Косвенным доказательством является то, что, в конце концов, они предназначены для сопоставления с функциями, обнаруженными в реальном оборудовании, и которые позволяют эффективно реализовывать алгоритмы синхронизации. Как вы говорите, ограждения, которые применяются только к определенным значениям, 1. бесполезны и 2. не обнаружены на текущем оборудовании.

Как говорится, в разделе AFAICS, который вы цитируете, описывается отношение "синхронизируется с" между заборами и атомарными операциями. Для определения того, что это значит, см. Раздел 1.10 Многопоточные выполнения и гонки данных . Опять же, AFAICS, это не означает, что заборы применяются только к атомным объектам, но, скорее, я подозреваю, что смысл в том, что, в то время как обычные нагрузки и накопители могут проходить, приобретать и отпускать заборы обычным способом (только в одном направлении), атомные нагрузки / магазины не могут.

Wrt. атомарные объекты, я понимаю, что на всех целевых объектах Linux поддерживаются правильно выровненные простые целочисленные переменные, чьи sizeof () <= sizeof (* void) являются атомарными, поэтому Linux использует нормальные целые числа в качестве переменных синхронизации (то есть, атомарные операции ядра Linux работают на нормальных целочисленных переменных). C ++ не хочет налагать такое ограничение, следовательно, отдельные атомарные целочисленные типы. Кроме того, в C ++ операции с атомарными целочисленными типами подразумевают барьеры, тогда как в ядре Linux все барьеры являются явными (что является очевидным, поскольку без поддержки компилятором атомарных типов это то, что нужно делать). </p>

...