Худший (на самом деле не будет работать)
Измените модификатор доступа counterнаpublic volatile
Как уже упоминали другие люди, это само по себе вообще не безопасно. Дело в volatileтом, что несколько потоков, работающих на нескольких процессорах, могут и будут кешировать данные и изменять порядок команд.
Если это не так volatile , и ЦП A увеличивает значение, то ЦП B может фактически не увидеть это увеличенное значение до некоторого времени спустя, что может вызвать проблемы.
Если это так volatile, то это гарантирует, что два процессора одновременно видят одни и те же данные. Это не мешает им чередовать операции чтения и записи, и это та проблема, которую вы пытаетесь избежать.
Второе место:
lock(this.locker) this.counter++;
Это безопасно (при условии, что вы помните, lockгде бы вы ни находились this.counter). Это препятствует тому, чтобы любые другие потоки выполнили любой другой код, который охраняется locker. Использование блокировок также предотвращает проблемы переупорядочения многопроцессорных систем, как описано выше, и это здорово.
Проблема в том, что блокировка медленная, и если вы повторно используете ее lockerв каком-то другом месте, которое на самом деле не связано, то вы можете в конечном итоге заблокировать другие потоки без причины.
Лучший
Interlocked.Increment(ref this.counter);
Это безопасно, поскольку эффективно выполняет чтение, приращение и запись в одно нажатие, которое не может быть прервано. Из-за этого это не повлияет на любой другой код, и вам также не нужно забывать блокировать в другом месте. Это также очень быстро (как говорит MSDN, на современных ЦП это часто буквально одна инструкция ЦП).
Я не совсем уверен, однако, если это обойти другие процессоры, переупорядочивающие вещи, или вам также нужно объединить volatile с приращением.
InterlockedNotes:
- Взаимосвязанные методы безопасны одновременно на любом количестве ядер или процессоров.
- Взаимосвязанные методы применяют полную границу вокруг выполняемых инструкций, поэтому изменение порядка не происходит.
- Методы с блокировкой не нуждаются или даже не поддерживают доступ к изменчивому полю , поскольку volatile размещает половину ограждения вокруг операций на заданном поле, а блокированное использует полное ограждение.
Сноска: Что на самом деле хорошо для изменчивого?
Как volatileне предотвратить такие проблемы многопоточности, для чего это нужно? Хорошим примером является то, что у вас есть два потока, один из которых всегда записывает в переменную (скажем queueLength), а другой всегда читает из этой же переменной.
Если queueLengthон не является энергозависимым, поток A может записывать пять раз, но поток B может видеть, что эти записи задерживаются (или даже могут быть в неправильном порядке).
Решением будет блокировка, но вы также можете использовать volatile в этой ситуации. Это гарантирует, что поток B всегда будет видеть самую последнюю информацию, которую написал поток A. Однако обратите внимание, что эта логика работает, только если у вас есть писатели, которые никогда не читают, и читатели, которые никогда не пишут, и если то, что вы пишете, является атомарной ценностью. Как только вы выполните одну операцию чтения-изменения-записи, вам нужно перейти к блокированным операциям или использовать блокировку.