Как атомарная операция не может быть операцией синхронизации? - PullRequest
1 голос
/ 20 марта 2019

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

Пример здесь тогда не даст ожидаемого результата, верно?

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

Возможно, я не понимаю, что означает синхронизация.Где дыра в моей логике?

Ответы [ 3 ]

2 голосов
/ 20 марта 2019

Компилятору и ЦПУ разрешено изменять порядок доступа к памяти.Это как если бы правило , и оно предполагает однопоточный процесс.

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

int x = 1;
std::atomic<int> y = 1;

    // Thread 1
    x++;
    y.fetch_add(1, std::memory_order_release);

    // Thread 2
    while ((y.load(std::memory_order_acquire) == 1)
    { /* wait */ }
    std::cout << x << std::endl;  // x is 2 now

В то время как с ослабленным порядком памяти мы получаем только атомарность, но не упорядочение

int x = 1;
std::atomic<int> y = 1;

    // Thread 1
    x++;
    y.fetch_add(1, std::memory_order_relaxed);

    // Thread 2
    while ((y.load(std::memory_order_relaxed) == 1)
    { /* wait */ }
    std::cout << x << std::endl;  // x can be 1 or 2, we don't know

Действительно, как объясняет Херб Саттер в своем превосходном выступлении Атомное оружие , memory_order_relaxed делает многопоточную программу очень трудной для рассуждения и должна использоваться только в очень конкретных случаях,когда нет никакой зависимости между атомарной операцией и любой другой операцией до или после нее в каком-либо потоке (очень редко бывает).

0 голосов
/ 20 марта 2019

Предположим, у нас есть

std::atomic<int> x = 0;

// thread 1
foo();
x.store(1, std::memory_order_relaxed);

// thread 2
assert(x.load(std::memory_order_relaxed) == 1);
bar();

Во-первых, нет никакой гарантии, что поток 2 будет соблюдать значение 1 (то есть утверждение может сработать).Но даже если поток 2 действительно наблюдает значение 1, в то время как поток 2 выполняет bar(), он может не наблюдать побочные эффекты, генерируемые foo() в потоке 1. И если foo() и bar() обращаются к одному и тому же неатомарномупеременных, может возникнуть гонка данных.

Теперь предположим, что мы изменили пример на:

std::atomic<int> x = 0;

// thread 1
foo();
x.store(1, std::memory_order_release);

// thread 2
assert(x.load(std::memory_order_acquire) == 1);
bar();

До сих пор нет гарантии, что поток 2 соблюдает значение 1;В конце концов, может случиться так, что загрузка происходит перед магазином.Однако в этом случае , если поток 2 наблюдает значение 1, то хранилище в потоке 1 синхронизируется с загрузкой в ​​потоке 2. Это означает, что все, что упорядочено до того, как хранилище в потоке 1 произойдет довсе, что упорядочено после загрузки в потоке 2. Следовательно, bar() увидит все побочные эффекты, вызванные foo(), и если они оба получат доступ к одним и тем же неатомарным переменным, гонка данных не произойдет.

Итак, как вы можете видеть, свойства синхронизации операций в x ничего не говорят о том, что происходит с x.Вместо этого синхронизация налагает порядок на , окружающий операции в двух потоках.(Следовательно, в связанном примере результат всегда равен 5 и не зависит от порядка в памяти; свойства синхронизации операций выборки-добавления не влияют на эффект самих операций выборки-добавления.)

0 голосов
/ 20 марта 2019

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

Например,

int k = 5;
void foo() {
    k = 10;
}

int baz() {
    return k;
}

При наличии нескольких потоков поведение не определено, поскольку оно выставляет состояние гонки.На практике на некоторых архитектурах может случиться так, что вызывающий baz увидит ни 10, ни 5, а какое-то другое, неопределенное значение.Его часто называют torn или dirty read.

Если вместо этого используется расслабленная атомная нагрузка и хранилище, baz будет гарантированно возвращать либо 5, либо 10,поскольку не было бы гонки данных.

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

...