Найгірше (насправді не вийде)
Змініть модифікатор доступу counter
наpublic volatile
Як вже згадували інші, це самостійно зовсім не є безпечним. Сенс у volatile
тому, що кілька потоків, що працюють на декількох процесорах, можуть і кешуватимуть дані та впорядковувати інструкції.
Якщо це не так volatile
, а CPU A збільшує значення, тоді CPU B може фактично не бачити це збільшення значення до деякого часу, що може спричинити проблеми.
Якщо це так volatile
, це просто забезпечує те, що два процесори бачать однакові дані одночасно. Це зовсім не заважає їм переплутати операції читання та запису, що є проблемою, якої ви намагаєтеся уникнути.
Другий кращий:
lock(this.locker) this.counter++
;
Це можна зробити безпечно (за умови, що ви пам’ятаєте про те, lock
де ви маєте доступ this.counter
). Це не дозволяє іншим потокам виконувати будь-який інший код, який захищається locker
. Використання блокувань також запобігає проблемам із упорядкуванням багатопроцесорних процесорів, як зазначено вище, що чудово.
Проблема полягає в тому, що блокування відбувається повільно, і якщо ви повторно користуєтесь locker
іншим місцем, яке не пов’язане насправді, ви можете в остаточному підсумку блокувати інші потоки без будь-якої причини.
Найкраще
Interlocked.Increment(ref this.counter);
Це безпечно, оскільки це ефективно робить читання, збільшення та запис у "один хіт", який неможливо перервати. Через це він не вплине на будь-який інший код, і вам також не потрібно пам'ятати, щоб заблокувати його. Це також дуже швидко (як каже MSDN, на сучасних процесорах це часто буквально одна інструкція процесора).
Однак я не зовсім впевнений, чи не обійдеться він із іншими процесорами, що перереєструють речі, або якщо вам також потрібно поєднувати непостійні з приростом.
InterlockedNotes:
- БЕЗПЕЧНІ МЕТОДИ ТОЧНО БЕЗПЕЧНІ НА БУДЬ-ЯКІЙ ЧАСТИНИ АБО ЦПУ.
- Заблоковані методи застосовують повну огорожу навколо інструкцій, які вони виконують, тому переупорядкування не відбувається.
- Методи блокування не потребують або навіть не підтримують доступ до мінливого поля , оскільки мінливий розміщується на половині огорожі навколо операцій над заданим полем, а перемикається - використовує повний паркан.
Виноска: те, що леткі, насправді добре.
Оскільки volatile
це не перешкоджає таким різновидам багатопотокових запитань, для чого це? Хороший приклад - це те, що у вас є два потоки, той, який завжди пише в змінну (скажімо queueLength
), і той, який завжди читається з тієї самої змінної.
Якщо queueLength
він не мінливий, нитка A може записати п'ять разів, але потік B може бачити ці записи затримкою (або навіть потенційно в неправильному порядку).
Рішенням може бути блокування, але ви також можете використовувати непостійні в цій ситуації. Це забезпечило б, що нитка B завжди побачить найновішу річ, яку написала нитка A. Однак зауважте, що ця логіка працює лише в тому випадку, якщо у вас є письменники, які ніколи не читають, і читачі, які ніколи не пишуть, і якщо річ, яку ви пишете, має атомне значення. Як тільки ви зробите одне читання-зміна-запис, вам потрібно перейти до операцій із замкненими блоками або скористатися блокуванням.