У меня есть пара разъяснений относительно приведенных ниже утверждений (источник - https : //mechanical-sympathy.blogspot.in/2011/09/single-writer-principle.html ) :

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

На x86 / x64 «нагрузки могут быть переупорядочены со старыми хранилищами» в соответствии с моделью памяти, поэтому барьеры памяти требуются, когда несколько потоков изменяют одни и те же данные в ядрах '

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

Таким образом, если мы строго поддерживаем одного писателя - мы можем фактически отказаться от практики использования «synchronized» как при чтении, так и при записи в примитивных блокировках. Мы можем полностью отказаться от «синхронизированных»?

3
IUnknown 28 Май 2017 в 05:48

2 ответа

Лучший ответ

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

Однако это не имеет значения для программ на Java, поскольку модель памяти Java, являющаяся частью спецификации языка Java, не дает таких гарантий для многопоточных программ. На самом деле, термин «барьер памяти» вообще не входит в спецификацию.

Вы должны понять, что написанный вами код Java не будет кодом x86 / x64, который будет выполнять ЦП. Оптимизированный нативный код не будет похож на ваш исходный код. Одна фундаментальная часть оптимизации кода состоит в том, чтобы исключить избыточные операции чтения и записи или даже условные части кода в предположении, что значения не изменяются случайным образом, что всегда правильно для однопоточного исполнения.

Этот оптимизированный код будет давать противоречивые результаты, если лежащие в основе предположения будут недействительными из-за многопоточных манипуляций без надлежащих потоковобезопасных конструкций. Это допустимое несоответствие в спецификации, поскольку модель памяти, обеспечивающая согласованные результаты любой ценой, приведет к резкому снижению производительности. Потокобезопасные конструкции, такие как синхронизация или энергозависимые операции записи и чтения, не только сообщают JVM, куда вставлять барьеры памяти, если этого требует базовая архитектура, но также и где и как ограничить оптимизацию кода.

Это причина, по которой а) необходимы надлежащие поточно-безопасные конструкции при манипулировании изменяемым общим состоянием и б) эти конструкции могут иметь потери производительности, даже если на уровне ЦП / аппаратного обеспечения не требуется никаких барьеров памяти.

2
Holger 30 Май 2017 в 12:16

Большой отказ от ответственности

Некоторые вещи, которые я здесь написал, были фактически протестированы - например, переупорядочение, очистка и т. Д .; некоторые из них заняли много времени на чтение, надеюсь, я понял их правильно.

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

Например:

 static int sum(int x, int y){
     x = x + 1;
     y = y + 1;
     return x + y;
 }

Тебя не волнует порядок , в котором эти операции выполняются, пока результат верен, не так ли?

Без барьеров памяти (часто называемых StoreLoad|StoreStore|LoadStore|LoadLoad) любая операция может измениться. Чтобы гарантировать, что некоторые операции не выполняются move beyond a fence, реализованы cpu fences. У Java есть несколько способов их генерации - volatile, synchroniztion, Unsafe/VarHandle (могут быть и другие, я не знаю).

В основном, когда вы пишете в volatile, например, это происходит:

volatile x...

[StoreStore] - inserted by the compiler
[LoadStore]
x  = 1; // volatile store
[StoreLoad] 

...

[StoreLoad]
int t = x; // volatile load
[LoadLoad]
[LoadStore]

Давайте возьмем подмножество этого примера:

[StoreStore]
[LoadStore]
x = 1; // volatile store

Это означает, что любая Store или Load переменной не может быть переупорядочена с x = 1. Тот же принцип применяется к другим барьерам.

Мартин Томсон говорит, что барьер on x86 3 из 4 БЕСПЛАТНЫЙ, единственное, что должно быть выдано: StoreLoad. Они бесплатны, потому что x86 имеет сильную модель памяти, что означает, что другие операции не переупорядочены по умолчанию . На других процессорах некоторые из этих операций также довольно дешевы (если я ошибаюсь в ARM есть lwsync - облегченная синхронизация; имя должно быть самоочевидным).

Кроме того, между процессором и кешем есть небольшой буфер - он называется Store Buffer. Когда вы записываете что-то в переменную, она не попадает напрямую в кеш (ы). Это идет в этот буфер. Когда он полон (или принудительно очищается через StoreLoad), он помещает записи в кеши - и cache coherency protocol может синхронизировать данные во всех кешах.

Что Мартин говорит, что если у вас есть несколько писателей, вы должны выпускать StoreLoad много раз - таким образом, это дорого. Если у вас есть один писатель, вам не нужно. Буфер будет опустошен, когда он заполнится. Когда это произойдет? Ну, иногда , теоретически может быть никогда, на практике довольно быстро.

Некоторые потрясающие ресурсы (они иногда держали меня всю ночь без сна, так что будьте осторожны!):

Это StoreStore кстати, каждый раз, когда вы пишете в конечную переменную внутри конструктора:

 private final int i ;

 public MyObj(int i){
     this.i = i;
      // StoreStore here 
}

LazySet

Шипилев Летучий

И мой любимый на все времена!

1
Eugene 30 Май 2017 в 14:38