Во-первых, заметим, что конечным получателем маркера потока, который был указан в качестве аргумента ключевого слова lock, является метод Monitor.Enter(). Во-вторых, весь программный код в рамках контекста соответствующей блокировки помещен в блок try. Соответствующий блок finally гарантирует, что маркер потока будет освобожден (с помощью метода Monitor.Exit()), независимо от исключений, которые могут возникать в среде выполнения. Если изменить программу MultiThreadSharedData так, чтобы тип Monitor использовался непосредственно (как это будет сделано чуть позже), то ее вывод останется тем же.
При использовании ключевого слова lock, кажется, требуется меньший ввод программного кода, чем при явном использований типа System.Threading.Monitor, поэтому вы можете задать вопрос о преимуществах непосредственного использования типа Monitor. Краткий ответ: контроль. При использовании типа Monitor вы можете дать указание активному потоку подождать (с помощью метода Wait()), информировать ожидающие потоки о завершении текущего потока (с помощью методов Pulse() и PulseAll()) и т.д.
В большинстве случаев вам будет вполне достаточно возможностей, обеспечиваемых ключевым словам C# lock. Но если вы захотите рассмотреть другие члены класса Monitor, обратитесь к документации .NET Framework 2.0 SDK.
Синхронизация с помощью типа System.Threading.Interlocked
В это всегда верится с трудом, пока вы не проверите соответствующий программный код CIL, но и операции присваивания, и базовые арифметические операции
Таблица 14.4. Члены типа System.Threading.Interlocked
| Член | Описание |
|---|---|
| CompareExchange() | Безопасно проверяет два значения на равенство, и если они равны, заменяет одно из значений третьим |
| Decrement() | Безопасно уменьшает значение на 1 |
| Exchange() | Безопасно меняет два значения местами |
| Increment() | Безопасно выполняет приращение значения на 1 |
Хотя это может и не казаться очевидным на первый взгляд, процесс атомарного изменения одного значения является вполне типичным в многопоточном окружении. Предположим, что у нас есть метод AddOne(), который увеличивает целочисленную переменную intVal на единицу. Вместо программного кода синхронизации, подобного следующему;
public void AddOne() {
lock(this) {
intVal++;
}
}
можно предложить более простой программный код, в котором используется статический метод Interlocked.Increment(). Просто передайте переменную для приращения по ссылке. Обратите внимание на то, что метод Increment() не только изменяет значение поступающего параметра, но и возвращает новое значение.
public void AddOne() {
int newVal = Interlocked.Increment(ref intVal);
}
В дополнение к Increment() и Decrement() тип Interlocked позволяет атомарно присваивать числовые и объектные данные. Например, если вы хотите присвоить члену-переменной значение 83, вы можете избежать необходимости явного использования оператора lock (или явного применения логики Monitor), если используете метод Interlocked.Exchange().
public void SafeAssignment() {
Interlocked.Exchange(ref myInt, 83);
}
Наконец, при проверке двух значений на равенство, чтобы обеспечить потоковую безопасность элементу сравнения, можете использовать метод Interlocked.CompareExchange(), как показано ниже.
public void CompareAndExchange() {
// Если значением i является 83, изменить его на 99.
Interlocked.CompareExchange(ref i, 99, 83);
}
Синхронизация с помощью атрибута [Synchronization]