День 1370. #ЗаметкиНаПолях #AsyncTips
Блокировки и команда lock
Задача
Имеются общие данные. Требуется обеспечить безопасное чтение и запись этих данных из нескольких потоков.
Решение
Лучшее решение в такой ситуации — использование команды блокировки lock. Если поток входит в блок lock, то все остальные потоки не смогут войти в этот блок, пока блокировка не будет снята:
class MyClass
{
// Блокировка защищает поле _value.
private readonly object _mutex = new object();
private int _value;
public void Increment()
{
lock (_mutex)
{
_value = _value + 1;
}
}
}
В .NET существует несколько механизмов блокировки: Monitor, Spin, Lock и ReaderWriterLockSlim. В большинстве приложений эти типы блокировок практически никогда не должны использоваться напрямую. В частности, разработчики часто используют ReaderWriterLockSlim, даже когда такая сложность не является необходимой. Базовая команда lock нормально справляется с 99% случаев.
При использовании блокировок следует руководствоваться четырьмя важными рекомендациями:
1. Ограничьте видимость блокировки.
Объект, используемый в команде lock, должен быть приватным полем, которое никогда не должно быть доступным извне класса. Обычно есть не более одного поля блокировки на тип; если у вас их несколько, рассмотрите возможность рефакторинга этого типа на несколько типов. Блокировка может устанавливаться по любому ссылочному типу, но рекомендуется создавать отдельное поле специально для команды lock, как в примере выше. Если вы устанавливаете блокировку по другому экземпляру, убедитесь в том, что он является приватным для вашего класса; он не должен передаваться в конструкторе или возвращаться из get-метода свойства. Никогда не используйте lock(this) или lock с любым экземпляром Type или string; это может привести к взаимоблокировкам, доступным из другого кода.
2. Оставьте комментарий, что именно защищает блокировка.
Об этом шаге легко забыть во время первоначального написания кода, но когда кодовая база вырастет, это может оказаться важным.
3. Сократите до минимума объем кода, защищённого блокировкой.
Один из аспектов, на которые следует обращать внимание, — блокирующие вызовы при удержании блокировок. В идеале их быть вообще не должно.
4. Никогда не выполняйте произвольный код при удержании блокировки.
Это включает выдачу событий, вызов виртуальных методов или вызов делегатов. Если вам потребуется выполнить произвольный код, сделайте это после снятия блокировки.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 12.