Худший (на самом деле не будет работать)
Изменить модификатор доступа 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. Однако обратите внимание, что эта логика только работает, если у вас есть писатели, которые никогда не читают, и читатели, которые никогда не пишут, и , если то, что вы пишете, имеет атомарную ценность. Как только вы выполните одну операцию чтения-изменения-записи, вам нужно перейти к блокированным операциям или использовать блокировку.