Возможны ли в данном случае взаимоблокировки потоков?

Если я не ставлю lock на получение, при этом у меня есть lock на изменение значения, в данном случае возможна взаимоблокировка или я ошибаюсь? Всегда ли нужно ставить lock как на получение, так и на изменение в ситуации с многопоточностью?

public class MyClass
{
    private object _lock = new object();

    private string _property;

    public string Property
    {
        get
        {
            // Получение без блокировки
            return _property;
        }
        set
        {
            lock (_lock)
            {
                // Изменение с блокировкой
                _property = value;
            }
        }
    }
}

Ответы (1 шт):

Автор решения: aepot

Для потокобезопасного измененения коллекций существуют потокобезопасные коллекции.

Но допустим речь не об этом, а об одном единственном свойстве. Как сказали в комментариях, присвоение ссылочного типа атомарно, а тип string неизменяемый, поэтому здесь lock не нужен в принципе.

Но представим что операция изменения свойства не является потокобезопасной, тогда здесь ошибка, вы не можете добиться потокобезопасности поместив в lock только сеттер, так как если запись не является потокобезопасной, то возможно, что произойдёт небезопасное чтение посередине записи.

Поэтому геттер тоже должен быть под локом.

public string Property
{
    get
    {
        lock (_lock)
        {
            // небезопасное чтение
        }
    }
    set
    {
        lock (_lock)
        {
            // небезопасная запись
        }
    }
}

Вы правильно подозреваете, что когда изменение свойства не происходит, то читать можно многопоточно не блокируя потоки между собой. Для этого существует шаблон проектирования Reader-Writer-Lock. Он говорит о том, что пока записи нет, любое количество потоков может читать данные, а когда требуется запись, нужно прекратить все чтения и писать только одним потом одновременно.

private readonly ReaderWriterLockSlim _lock = new();

public string Property
{
    get
    {
        _lock.EnterReadLock();
        try
        {
            // небезопасное чтение
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }
    set
    {
        _lock.EnterWriteLock();
        try
        {
            // небезопасная запись
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }
}

В этом случае когда нет записи, блокировок при чтении вообще не будет. Само собой, что если есть гарантия, что операция чтения/записи никогда не выбросит исключение, то оборачивать её в try-finally не требуется.

→ Ссылка