Блокується і мінливий


78

У мене є змінна, яку я використовую для представлення стану. Його можна читати та записувати з декількох потоків.

Я використовую Interlocked.Exchangeі, Interlocked.CompareExchangeщоб змінити це. Однак я читаю це з декількох потоків.

Я знаю, що це volatileможе бути використано для переконання, що змінна не кешована локально, а завжди читає безпосередньо з пам'яті.

Однак якщо я встановлюю змінну на volatile, тоді вона генерує попередження про використання volatile і передачу за допомогою ref до методів Interlocked.

Я хочу переконатися, що кожен потік читає останнє значення змінної, а не якусь кешовану версію, але я не можу використовувати volatile.

Є, Interlocked.Read але це для 64-розрядних типів, і він недоступний на компактній платформі. У документації до нього сказано, що він не потрібен для 32-бітових типів, оскільки вони вже виконуються за одну операцію.

В Інтернеті є заяви, що вам не потрібні нестабільні, якщо ви використовуєте взаємоблоковані методи для всього свого доступу. Однак ви не можете прочитати 32-бітну змінну за допомогою методів Interlocked, тому ви не можете використовувати методи Interlocked для всього свого доступу.

Чи є якийсь спосіб здійснити безпечне читання та запис моєї змінної без використання блокування?


2
Насправді гарне запитання. Використання звичайного блокування означає критичну точку виконання та гарантує, що ваше значення буде актуальним для всіх потоків. Однак Interlocked.Exchange не реалізовано з використанням, lockі я не можу знайти посилання на те, що він дає такі гарантії.
Торарін,

Відповіді:


45

Ви можете сміливо нехтувати цим попередженням, коли використовуєте Interlocked.Xxxфункції (див. Це запитання ), оскільки вони завжди виконують нестабільні операції. Отже, volatileзмінна цілком нормальна для спільного стану. Якщо ви хочете позбутися застереження будь-якою ціною, ви насправді можете виконати блоковане читання Interlocked.CompareExchange (ref counter, 0, 0).

Редагувати: Насправді вам потрібна volatileваша змінна стану лише в тому випадку, якщо ви збираєтеся писати на неї безпосередньо (тобто не використовуючи Interlocked.Xxx). Як згадано jerryjvl , читання змінної, оновленої блокованою (або нестабільною) операцією, використовуватиме найновіше значення.


Де мені слід замінити 0 на значення, яке моєю змінною ніколи не буде?
трампстер

3
Фу, якби цього не сталося, Interlocked був би досить марним. Налякав мене там на хвилину :)
Thorarin

@Daniel: ні, константа не має значення, оскільки CompareExchangeбуде замінюватися 0на 0if counterє 0- тобто не-op.
Антон Тихий

2
Чи volatileзахищає незмінна змінну захист від оптимізації компілятором або JIT? Чи не є правильним посилання на @jerryjvl?
binki 02

Скажімо, xце спільна змінна. В одному потоці я a+=x; a+=x;з aлокальності до нитки. У другій темі я це роблю Interlocked.Increment(ref x);. Без volatileцього перший потік може помістити xв регістр і використовувати його для обох своїх операторів, відсутній приріст. Так volatileздається необхідним, навіть з Interlocked.
wezten

45

Заблоковані операції та летючі властивості насправді не повинні використовуватися одночасно. Причиною отримання попередження є те, що воно (майже?) Завжди вказує на те, що ви неправильно зрозуміли, що робите.

Надто спрощення та перефразування:
volatileвказує на те, що кожна операція читання потребує повторного читання з пам'яті, оскільки можуть бути інші потоки, що оновлюють змінну. При застосуванні до поля, яке можна читати / писати атомно за допомогою архітектури, на якій ви працюєте, це повинно бути все, що вам потрібно зробити, якщо ви не використовуєте long / ulong, більшість інших типів можна читати / писати атомно.

Коли поле не позначене летким, ви можете використовувати Interlockedоперації, щоб зробити подібну гарантію, оскільки це спричиняє очищення кеш-пам’яті, щоб оновлення було видимим для всіх інших процесорів ... це має ту перевагу, що ви ставите накладні витрати оновлення, а не прочитане.

Який із цих двох підходів буде найкращим, залежить від того, що саме ви робите. І це пояснення є грубим надмірним спрощенням. Але з цього повинно бути зрозуміло, що робити обидва одночасно безглуздо.


2
По суті, немає нічого поганого у використанні летких та блокованих разом. Причина, по якій ви отримуєте попередження, полягає в тому volatile, що volatileвона не є частиною системи типів, і викликана особа, яка отримує посилання, не буде знати, що на вказану змінну потрібен нестабільний доступ.
Антон Тихий

2
Для протоколу, тут є дискусія, де припускають, що ця відповідь неправильна. @jerryjvl, якщо ви хочете зробити свій внесок, це було б чудово.
Роман Старков

2
У цьому є більше тонкощів, ніж я уявляв, коли вперше писав цю відповідь. Я настійно рекомендую прочитати Джо Даффі (та / або його книгу про паралельність), щоб отримати детальне розуміння в контексті вирішуваної проблеми. Він справді авторитет, коли справа стосується цих питань. Основна проблема безблокування полягає в тому, що, як правило, мова йде про продуктивність в якомусь сенсі, але на цьому шляху є підводні камені, які можуть або дати неправильні результати, або погіршити потрібну продуктивність.
jerryjvl

9
Джо Даффі конкретно розповідає про посилання на нестабільну / блоковану операцію в цьому записі в блозі . Змішування, пов’язане з посиланнями, на 100% нормальне, і він подав клопотання про додавання особливого випадку у компіляторі C #, щоб у цьому випадку не видавати попередження.
Чуу

3
Поточні посилання на блог Джо Даффі у його новому будинку joeduffyblog.com/2008/06/13/… joeduffyblog.com/2009/02/02/… joeduffyblog.com/2007/11/10/clr-20-memory-model
CCondron,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.