Найгірше (насправді не вийде)
Змініть модифікатор доступу 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. Однак зауважте, що ця логіка працює лише в тому випадку, якщо у вас є письменники, які ніколи не читають, і читачі, які ніколи не пишуть, і якщо річ, яку ви пишете, має атомне значення. Як тільки ви зробите одне читання-зміна-запис, вам потрібно перейти до операцій із замкненими блоками або скористатися блокуванням.