Здравствуйте, ., Вы писали:
.>Здравствуйте, Pavel Dvorkin, Вы писали:
PD>>Имеется в виду, что замена ссылки всегда атомарна, как утверждает Sun. Но если она не volatile, то это просто замена 4(8) байт. А если volatile, то (по-вашему) еще и сброс всего кеша всех тредов. В это я как-то плохо верю. .>Не всех, а тех, которые обращаются к этой volatile переменной в данный момент.
Что значит "тех, которые обращаются " ? Обращается-то один. И зачем его кеш сбрасывать, когда надо сбрасывать кеш того потока, который по этой volatile ссылке что-то там модифицировал ?
>И не всего кеша, а тот который должен happens-before обращения к этой переменной.
Это как, можно узнать ? Кеш ядра прозрачен для программы, никто не знает, какая переменая где в кеше находится и есть ли она там вообще.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>С++ тут ни при чем. С таким же успехом можно писать на ассемблере, где локов вообще нет, или на Delphi, где их тоже нет. Как и в С++.
В ассемблере вообще-то специальные инструкции есть для барьеров. Но я тут не копенгаген.
PD>То есть ты утверждаешь что при любом обращении к synchronized переменной вся паять всех кешей сбрасывается в RAM ? Иного объяснения я не вижу. Знать, какой поток изменил данные по этой synchronized ссылке никто не может.
Сам поток при входе-выходе знает. Не понимаю зачем это знать другим потокам. Посмотри вот этот код:
int a=0, b=0;
...
a=1;
//synchronized(this){
b=1;
//}
если переменные non-volatile то если один поток прочитает b и там будет 1, то переменная a может иметь значение 0. Хотя это выглядит дико, но оптимизаторы/кеши имеют право.
Если убрать комментарии или переменную b объявить volatile, то спецификация языка java 1.5 гарантирует что a==1.
Всё абсолютно то же самое и в C++ c синхронизацией. Только есть разница для volatile — там гарантии тоже нет для некоторых компиляторов.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
EK>>Ну не меняется и отлично. Значит используйте synchronized. Я лишь пытался объяснить что volatile это не только memory consistency но и happens-before. Судя по вашим предыдущим комментариям вы этот момент упускаете.
PD>Я об этом и спрашиваю. Как и когда реализуется этот самый happen-before ? Кто его организует для всего графа, висящего на этой ссылке ?
Зависит от реализации VM.
PD>Обращение к volatile сбрасывает кеш всех ядер ? Да или нет ?
По спецификации обязано сбрасывать только кеш того потока который делает volatile-read и только того состояния которое было изменено до последнего volatile-write.
PD>Можно дать определение общего состояния ?
Мутабельное (изменяемое) состояние к которому могут иметь одновременный доступ (на запись или чтение) два и более потока исполнения.
PD>Когда и как оно синхронизируется.
Когда — при чтении или записи данных.
Как — synchronized, volatile или java.util.concurrent.lock.* в зависимости от задачи. Есть еще AtomicXXX классы которые обеспечивают доступ к общему состоянию без необходимости синхронизации
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
PD>>То есть при обращении к volatile переменной сбрасываются все изменения в кэше всех потоков ? При любом обращении ?
EK>Только в том потоке в котором вы читаете. И только те изменения которые были сделаны до записи volatile.
Что в том потоке ? Он обращается к volatile переменной. Она ссылается на данные, изготовленные другим потоком. каким — бог знает. Поэтому данные в кеше неизвестно какого потока.
PD>>Аргументируйте. А если эта ссылка косвенно ссылается на 200 Мб всяких данных ?
EK>Значит все 200МБ будут видимы вашему потоку. Конкретная реализация JVM может вообще решить не использовать никакого кеша потока для этих данных — кто его знает? Главное — то, что вам гарантирует спецификация языка.
Что она опять же гарантирует ? Что при обращении к synchronized переменной все данные, доступные из нее, будут видны потоку, который выполнил это обращение ?
EK>>>Вообще не понял что имеется ввиду.
PD>>Имеется в виду, что замена ссылки всегда атомарна, как утверждает Sun. Но если она не volatile, то это просто замена 4(8) байт. А если volatile, то (по-вашему) еще и сброс всего кеша всех тредов. В это я как-то плохо верю.
EK>Не всего кеша и не всех потоков. Не выкручивайте ответы наизнанку.
Я ничьи ответы не выкручиваю. Я просто объясняю, что иначе это сделать невозможно. IMHO. Если считаете, что возможно — объясните как.
Может, я плохо понял, но там опять о самой ссылке, а не о том, на что она ссылается.
Здравствуйте, ., Вы писали:
>>Иного объяснения я не вижу. Знать, какой поток изменил данные по этой synchronized ссылке никто не может. .>Сам поток при входе-выходе знает.
Я уже не знаю, как объяснять. Давай закончим.
.>Всё абсолютно то же самое и в C++ c синхронизацией. Только есть разница для volatile — там гарантии тоже нет для некоторых компиляторов.
Я об одном, ты совсем о другом. Мы друг друга не понимаем.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Что в том потоке ? Он обращается к volatile переменной. Она ссылается на данные, изготовленные другим потоком. каким — бог знает. Поэтому данные в кеше неизвестно какого потока.
Он обращается к значению volatile переменной, которая была до этого записана другим потоком. Когда другой поток записывал, он сбросил все свои кеши, которые были happens-before. Где чего неизвестно??
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Здравствуйте, StanislavK, Вы писали:
PD>>>Для уточнения — такой вопрос. Пусть я в одном потоке записал элементы некоего 100 Кб массива. Что я должен сделать, чтобы другой поток гарантированно увидел эти данные ? SK>>Общее правило такое — изменения флешатся на записи в volatile и на освобождении лока. PD>Этого мало.
Мало чего?
PD>Они должны флешиться при чтении volatile переменной, причем для всех потоков (точнее, всех кешей всех ядер).
Кому должны? Флешиться она должны так как описано в спеке и не более. И не понятно, как соотносятся "все потоки" и "все ядра".
PD>Пусть имеем volatile Clazz c; Поток T1 под нее выделил new Clazz и в него что-то записал, может, в класс X, доступный через Clazz через 5 промежуточных классов. Эти данные то ли в RAM, то ди в кеще (working storage) потока T1. PD>Поток T2 обращается к c. Обращается, а не меняет ее! После этого он по ней полезет в X. При этом должны сброситься в кеш все изменения в Clazz, X и всех 5 промежуточных классах. Но обходить граф никто не будет, да и смысла нет. Значит, должен сбрасываться весь кеш. А поскольку при обращении к c неизвестно, какой поток до этого имел с ней дело, кеш надо сбрасываать у всех ядер.
Чтение volatile гарантирует, что произойдет "запись" обратно из "главной памяти" в "память птока". Запись гарантирует обраное — "запись" из "память потока" в "главную память".
В описаном сценарии получается, что когда поток T1 записал в volatile, то его пямять флешнулась в "главную память". Если он после этого делал изменения, то не факт, что они будут в "главной памяти".
Когда T2 обращается к volatile, то гарантируется, что он увидит то, что было в "главной памяти" на момент обращания.
PD>Так ?
Не уверен. Не понимаю при чем тут все ядра.
Здравствуйте, Pavel Dvorkin, Вы писали:
>>>Иного объяснения я не вижу. Знать, какой поток изменил данные по этой synchronized ссылке никто не может. .>>Сам поток при входе-выходе знает. PD>Я уже не знаю, как объяснять. Давай закончим.
Ты по-моему на процесс с другой стороны смотришь вот и странным кажется. Там всё наоборот. Не поток "вытягивает" данные из других потоков, а наоборот, поток помещает/забирает данные в/из разделяемую память в точно обозначенные кодом моменты времени.
.>>Всё абсолютно то же самое и в C++ c синхронизацией. Только есть разница для volatile — там гарантии тоже нет для некоторых компиляторов. PD>Я об одном, ты совсем о другом. Мы друг друга не понимаем.
Ну ладно, не знаю тогда что не ясно. Вроде всё просто. Видимо ты не до конца понимаешь как оно всё работает и в С++. Почитай литературу, там всё более чётко объясняется.
Если что, показывай пример кода, помедитируем.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, StanislavK, Вы писали:
PD>>Пусть имеем volatile Clazz c; Поток T1 под нее выделил new Clazz и в него что-то записал, может, в класс X, доступный через Clazz через 5 промежуточных классов. Эти данные то ли в RAM, то ди в кеще (working storage) потока T1. PD>>Поток T2 обращается к c. Обращается, а не меняет ее! После этого он по ней полезет в X. При этом должны сброситься в кеш все изменения в Clazz, X и всех 5 промежуточных классах. Но обходить граф никто не будет, да и смысла нет. Значит, должен сбрасываться весь кеш. А поскольку при обращении к c неизвестно, какой поток до этого имел с ней дело, кеш надо сбрасываать у всех ядер.
SK>Чтение volatile гарантирует, что произойдет "запись" обратно из "главной памяти" в "память птока". Запись гарантирует обраное — "запись" из "память потока" в "главную память".
SK>В описаном сценарии получается, что когда поток T1 записал в volatile, то его пямять флешнулась в "главную память".
Так, допустим.
>Если он после этого делал изменения, то не факт, что они будут в "главной памяти".
Тоже допустим.
SK>Когда T2 обращается к volatile, то гарантируется, что он увидит то, что было в "главной памяти" на момент обращания.
Резюме.
Поток Т1 записал в volatile, его память переписана в RAM. Он потом делал изменения по этой ссылке в классе X, но не менял саму ссылку (то есть не менял volatile переменную, а менял данные, по ней доступные по ссылке). Поток T2 обратился к этим данным и увидел то, что было до этих изменений. (может, конечно, и изменения увидел, но не гарантируется)
Я ничего не переврал ? Вроде нет.
Тогда возвращаемся к исходному вопросу в моем примере. Как мне гарантировать, что изменения в классе X, которые он сделал после изменения volatile переменной, будут видны T2 ?
Более того, как мне гарантировать, что изменения в классе X, которые он сделал, в случае , если он вообще не менял саму ссылку c , будут видны потоку T2 ?
PD>>Так ? SK>Не уверен. Не понимаю при чем тут все ядра.
Здравствуйте, ., Вы писали:
.>Если что, показывай пример кода, помедитируем.
Помедитируй на моем примере с массивом и попробуй объяснить (себе) как изменения в нем окажутся видны первому потоку и почему. Ссылка там одна-единственная на все про все.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Я ничего не переврал ? Вроде нет.
Да. Ок.
PD>Тогда возвращаемся к исходному вопросу в моем примере. Как мне гарантировать, что изменения в классе X, которые он сделал после изменения volatile переменной, будут видны T2 ? PD>Более того, как мне гарантировать, что изменения в классе X, которые он сделал, в случае , если он вообще не менял саму ссылку c , будут видны потоку T2 ?
Залочить оба треда на одном локе перед изменением и перед чтением этих данных.
При освобождении лока пишуший тред сбросит все happens-before данные в главную память и читающий тред при взятии лока прочтёт их из главной памяти.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Pavel Dvorkin, Вы писали:
.>>Если что, показывай пример кода, помедитируем. PD>Помедитируй на моем примере с массивом и попробуй объяснить (себе) как изменения в нем окажутся видны первому потоку и почему. Ссылка там одна-единственная на все про все.
Без локов — могут не оказаться!
Чтобы гарантированно оказались видны ты обязан использовать lock. твой sleep(10000) вообще ничего не гарантирует.
Ещё как вариант (только если ты хочешь избежать синхронизации использованием lock-free для улучшение перформанса) — объявить все меняемые данные как volatile, но это невозможно для элементов массива, поэтому если только смотреть в сторону AtomicIntegerArray.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ., Вы писали:
PD>>Тогда возвращаемся к исходному вопросу в моем примере. Как мне гарантировать, что изменения в классе X, которые он сделал после изменения volatile переменной, будут видны T2 ? PD>>Более того, как мне гарантировать, что изменения в классе X, которые он сделал, в случае , если он вообще не менял саму ссылку c , будут видны потоку T2 ? .>Залочить оба треда на одном локе перед изменением и перед чтением этих данных. .>При освобождении лока пишуший тред сбросит все happens-before данные в главную память и читающий тред при взятии лока прочтёт их из главной памяти.
Вот это понятно. Логично. Если лочить код, исполняемый потоком, то при выходе из лока он может сбросить кеш в ОП. Аналоги в Win32 — Release(Mutex, Semaphor), LeaveCriticalSection, SetEvent...
То есть объявление synchronized ссылки для этой памяти ни чего в этом плане не дает ? Здесь же нет кода. Доступ к этой переменной был давным-давно, изменения вносились после доступа.
Здравствуйте, ., Вы писали:
.>Без локов — могут не оказаться! .>Чтобы гарантированно оказались видны ты обязан использовать lock. твой sleep(10000) вообще ничего не гарантирует.
Вот теперь ясно. syncronized переменная ничего не гарантирует в этом плане, нужен лок. Не для блокировки чего-то, а для сброса кеша при выходе из него.
.>Ещё как вариант (только если ты хочешь избежать синхронизации использованием lock-free для улучшение перформанса) — объявить все меняемые данные как volatile, но это невозможно для элементов массива, поэтому если только смотреть в сторону AtomicIntegerArray.
Ну массив здесь только для примера. А если это ссылка не на массив, а на развесистое дерево (граф) экземпляров классов ? Например, ссылка на экземпляр класса, в котором мэп из бог знает чего, а это бог знает что содержит мэп бог знает чего еще раз. Не могу же я все лочить, да и нет у меня доступа к внутренним полям этих мэпов.
(Про мэпы сказал просто для примера, не надо рассказывать по конкурентные мэпы
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>То есть объявление synchronized ссылки для этой памяти ни чего в этом плане не дает ? Здесь же нет кода. Доступ к этой переменной был давным-давно, изменения вносились после доступа.
"synchronized"? Не понял. Код давай. syncrhronized и есть Aquire/Release пара.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ., Вы писали:
.>Здравствуйте, Pavel Dvorkin, Вы писали:
PD>>То есть объявление synchronized ссылки для этой памяти ни чего в этом плане не дает ? Здесь же нет кода. Доступ к этой переменной был давным-давно, изменения вносились после доступа. .>"synchronized"? Не понял. Код давай. syncrhronized и есть Aquire/Release пара.
Держи
class A
{
synchronized int[] a = new int[1000];
int s;
void main()
{
Thread2.start();
f();
Thread2.join();
}
void f() // вызывается из mainThread
{
for (int i = 0; i < 1000; i++)
a[i] = i;
// поток mainThread изменил память, но, возможно, эти изменения только в его кеше, а не в RAM.
}
int sum() // вызывается из Thread2
{
Thread.sleep(10000);
// поток Thread2 обращается к этой памяти, но, возможно, ее содержимое неверно.for (int i = 0; i < 1000; i++)
s+=i;
}
synchronized ссылка есть. Менять ее никто не меняет. Меняют память по ней. Код, меняющий память, не запрашивает лока явно.
Здравствуйте, Pavel Dvorkin, Вы писали:
.>>Без локов — могут не оказаться! .>>Чтобы гарантированно оказались видны ты обязан использовать lock. твой sleep(10000) вообще ничего не гарантирует. PD>Вот теперь ясно. syncronized переменная ничего не гарантирует в этом плане, нужен лок. Не для блокировки чего-то, а для сброса кеша при выходе из него.
, где ($monitor — скрытая структура JVM которая есть у каждого объекта в яве).
т.е. я не понял твой вопрос, т.к. ты видимо не понимаешь что делает synchronized. или ты хотел сказать volatile?
PD>Ну массив здесь только для примера. А если это ссылка не на массив, а на развесистое дерево (граф) экземпляров классов ? Например, ссылка на экземпляр класса, в котором мэп из бог знает чего, а это бог знает что содержит мэп бог знает чего еще раз. Не могу же я все лочить, да и нет у меня доступа к внутренним полям этих мэпов. PD>(Про мэпы сказал просто для примера, не надо рассказывать по конкурентные мэпы
Ты можешь лочить все обращения к этой развесистой структуре.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай