Я прочитав деякі статті про volatile
ключове слово, але не зміг зрозуміти його правильне використання. Скажіть, будь ласка, для чого він повинен використовуватися в C # та на Java?
Я прочитав деякі статті про volatile
ключове слово, але не зміг зрозуміти його правильне використання. Скажіть, будь ласка, для чого він повинен використовуватися в C # та на Java?
Відповіді:
Як для C #, так і для Java, "непостійний" повідомляє компілятору, що значення змінної ніколи не слід кешувати, оскільки її значення може змінюватися поза межами сфери самої програми. Потім компілятор уникне будь-яких оптимізацій, які можуть спричинити проблеми, якщо змінна зміниться "поза її контролем".
Розглянемо цей приклад:
int i = 5;
System.out.println(i);
Компілятор може оптимізувати це для простого друку 5, як це:
System.out.println(5);
Однак якщо є інший потік, який може змінитися i
, це неправильна поведінка. Якщо інший потік зміниться i
на 6, оптимізована версія все ще надрукує 5.
volatile
Ключове слово запобігає таку оптимізацію і кешування, і , таким чином , є корисним , коли змінна може бути змінена іншим потоком.
i
позначкою як volatile
. На Яві все йде про стосунки, що трапляються раніше .
i
це локальна змінна, жоден інший потік не може її змінити. Якщо це поле, компілятор не може оптимізувати виклик, якщо це не так final
. Я не думаю, що компілятор не може робити оптимізацію, грунтуючись на припущенні, що поле "виглядає", final
коли воно прямо не оголошено як таке.
Щоб зрозуміти, що мінливий робить змінною, важливо зрозуміти, що відбувається, коли змінна не є мінливою.
Коли два потоки A & B звертаються до енергонезамінної змінної, кожен потік буде підтримувати локальну копію змінної у своєму локальному кеші. Будь-які зміни, внесені потоком A в її локальний кеш, не будуть видимі для потоку B.
Коли змінні оголошуються мінливими, це по суті означає, що потоки не повинні кешувати таку змінну, або іншими словами, потоки не повинні довіряти значенням цих змінних, якщо вони не зчитуються безпосередньо з основної пам'яті.
Отже, коли зробити змінну мінливою?
Коли у вас є змінна, до якої можна отримати доступ до багатьох потоків, і ви хочете, щоб кожен потік отримував останнє оновлене значення цієї змінної, навіть якщо значення оновлюється будь-яким іншим потоком / процесом / поза програмою.
Читання летючих полів набули семантики . Це означає, що гарантується, що зчитування пам'яті з мінливої змінної відбудеться перед читанням наступної пам'яті. Це перешкоджає компілятору виконувати переупорядкування, і якщо обладнання вимагає цього (слабо впорядкований процесор), він використовуватиме спеціальну інструкцію, щоб змусити апаратне очищення будь-яких зчитувань, які відбулися після нестабільного читання, але спекулятивно розпочалися рано, або процесор міг би перешкоджають їх видачі в першу чергу, запобігаючи виникненню будь-якого спекулятивного навантаження між питанням придбання вантажу та його відходом.
Записи летючих полів мають семантику випуску . Це означає, що гарантується, що будь-яке записування пам'яті у мінливу змінну гарантовано буде відкладено до тих пір, поки всі попередні записи пам'яті не стануть видимими для інших процесорів.
Розглянемо наступний приклад:
something.foo = new Thing();
Якщо foo
змінна-член класу, а інші процесори мають доступ до екземпляра об'єкта, на який посилається something
, вони можуть побачити foo
зміну значення, перш ніж запис пам'яті в Thing
конструкторі буде глобально видимим! Ось що означає «слабо упорядкована пам’ять». Це може статися, навіть якщо компілятор має всі склади в конструкторі до магазину foo
. Якщо foo
це, volatile
то в магазині foo
буде випущена семантика випуску, а апаратне забезпечення гарантує, що всі записи перед записом foo
будуть видимими для інших процесорів, перш ніж дозволяти запису foo
відбуватися.
Як можливо, щоб записи foo
писали так погано впорядковано? Якщо вміст рядка кеш-пам'яті foo
знаходиться в кеші, а сховища в конструкторі пропустили кеш-пам'ять, можливо, магазин зберігається набагато швидше, ніж записи в кеш пропускають.
(Жахлива) архітектура Itanium від Intel мала впорядковану пам'ять. Процесор, використовуваний в оригінальному XBox 360, мав слабко упорядковану пам'ять. Багато процесорів ARM, включаючи дуже популярний ARMv7-A, мають слабку впорядковану пам'ять.
Розробники часто не бачать цих перегонів даних, оскільки такі речі, як блокування, створюють повний бар'єр пам’яті, по суті те саме, що придбати та випустити семантику одночасно. Ніякі вантажі всередині замка не можуть бути спекулятивно виконані до придбання замка, вони затримуються до придбання замка. Жоден магазин не може затримуватися через випуск блокування, інструкція, яка звільняє блокування, затримується, поки всі записи, зроблені всередині блокування, не будуть видимими у всьому світі.
Більш повним прикладом є модель "Подвійне перевірене блокування". Мета цієї схеми - уникнути необхідності завжди отримувати замок, щоб ліниво ініціалізувати об'єкт.
Зафіксовано з Вікіпедії:
public class MySingleton {
private static object myLock = new object();
private static volatile MySingleton mySingleton = null;
private MySingleton() {
}
public static MySingleton GetInstance() {
if (mySingleton == null) { // 1st check
lock (myLock) {
if (mySingleton == null) { // 2nd (double) check
mySingleton = new MySingleton();
// Write-release semantics are implicitly handled by marking
// mySingleton with 'volatile', which inserts the necessary memory
// barriers between the constructor call and the write to mySingleton.
// The barriers created by the lock are not sufficient because
// the object is made visible before the lock is released.
}
}
}
// The barriers created by the lock are not sufficient because not all threads
// will acquire the lock. A fence for read-acquire semantics is needed between
// the test of mySingleton (above) and the use of its contents. This fence
// is automatically inserted because mySingleton is marked as 'volatile'.
return mySingleton;
}
}
У цьому прикладі сховища в MySingleton
конструкторі можуть не бути видимими для інших процесорів до зберігання mySingleton
. Якщо це станеться, інші потоки, що заглядають на mySingleton, не придбають замок, і вони не обов'язково підхоплять запис до конструктора.
volatile
ніколи не запобігає кешування. Те, що він робить, - це гарантувати порядок, в який "бачать" інші процесори. Випуск магазину затримує сховище до тих пір, поки всі очікувані записи не будуть завершені і не буде видано цикл шини, який повідомляє іншим процесорам скасувати / записувати їх кеш-рядок, якщо у них трапляється кешування відповідних рядків. Придбання вантажу вимиє будь-які роздуми, що читаються, гарантуючи, що вони не будуть усталеними значеннями минулого.
head
і виробництво tail
повинно бути мінливим, щоб не допустити, що виробник припуститься tail
, що не зміниться, і щоб споживач не припускав head
, що не зміниться. Крім того, вони head
повинні бути мінливими, щоб гарантувати, що записи даних черги будуть видимими у всьому світі, перш ніж магазин head
буде глобально видимим.
Летюча ключове слово має різні значення в обох Java і C #.
Поле може бути оголошено мінливим, і в цьому випадку модель пам'яті Java гарантує, що всі потоки бачать послідовне значення для змінної.
З посилання на C # на летюче ключове слово :
Ефірне ключове слово вказує на те, що поле може бути змінено в програмі чимось таким, як операційна система, апаратне забезпечення або одночасно виконуваний потік.
У Java "летюча" використовується для того, щоб сказати JVM, що змінна може використовуватися одночасно декількома потоками, тому певні загальні оптимізації неможливо застосувати.
Зокрема, ситуація, коли два потоки, що мають доступ до однієї змінної, працюють на окремих процесорах в одній машині. Це дуже часто для процесора, щоб він кешував агресивно дані, які він зберігає, оскільки доступ до пам'яті набагато повільніше, ніж доступ до кешу. Це означає, що якщо дані оновлюються в CPU1, вони повинні негайно пройти через усі кеші та в основну пам'ять, а не тоді, коли кеш вирішить очистити себе, щоб CPU2 побачив оновлене значення (знову ж таки, нехтуючи всіма кешами на шляху).
Коли ви читаєте нелетучі дані, виконуючий потік може або не завжди отримує оновлене значення. Але якщо об’єкт є летючим, нитка завжди отримує найсучасніше значення.
Нестабільна - це вирішення задачі про одночасність. Щоб це значення було синхронізовано. Це ключове слово в основному використовується для нарізування різьби. При декількох потоках оновлення однієї змінної.