Подтвердить что ты не робот

Почему С# не позволяет заблокировать нулевое значение?

С# не разрешает блокировку по нулевому значению. Я полагаю, я мог проверить, является ли значение нулевым или нет, прежде чем я его заблокирую, но поскольку я не заблокировал его, другой поток мог бы прийти и сделать значение null! Как я могу избежать этого состояния гонки?

4b9b3361

Ответ 1

Блокировать значение, которое никогда не является нулевым, например

Object _lockOnMe = new Object();
Object _iMightBeNull;
public void DoSomeKungFu() {
    if (_iMightBeNull == null) {
        lock (_lockOnMe) {
            if (_iMightBeNull == null) {
                _iMightBeNull = ...  whatever ...;
            }
        }
    }
}

Также будьте осторожны, чтобы избежать этого интересного состояния гонки с двойной проверкой блокировки: Гарантии модели памяти в блокировке с двойным проверкой

Ответ 2

Вы не можете заблокировать нулевое значение, потому что CLR не имеет места для привязки SyncBlock, что позволяет CLR синхронизировать доступ к произвольным объектам через Monitor.Enter/Exit (что является тем, что lock использует внутренне)

Ответ 3

Здесь есть две проблемы:

Во-первых, не блокируйте объект null. Не имеет смысла, как можно дифференцировать два объекта, как null?

Во-вторых, чтобы безопасно инициализировать переменную в многопоточной среде, используйте шаблон с двойной проверкой:

if (o == null) {
    lock (lockObj) {
        if (o == null) {
            o = new Object();
        }
    }
}

Это гарантирует, что другой поток еще не инициализировал объект и может использоваться для реализации шаблона Singleton.

Ответ 4

Почему С# не разрешает блокировку нулевого значения?

Ответ Павла является единственным технически правильным, поэтому я бы согласился с этим. Это связано с тем, что мониторы в .NET используют блок синхронизации, который привязан ко всем ссылочным типам. Если у вас есть переменная null, то она не относится ни к одному объекту, и это означает, что монитор не имеет доступа к используемому блоку синхронизации.

Как я могу избежать этого состояния гонки?

Традиционный подход заключается в блокировке ссылки на объект, которая, как вы знаете, никогда не будет null. Если вы окажетесь в ситуации, когда это невозможно гарантировать, я бы классифицировал это нетрадиционный подход. Там действительно не так много, о чем я могу упомянуть здесь, если вы более подробно не расскажете о конкретном сценарии, который может привести к целям блокировки с нулевым значением.

Ответ 5

Первая часть вашего вопроса уже ответила, но я хотел бы добавить что-то для второй части вашего вопроса.

Проще использовать другой объект для выполнения блокировки, особенно в этом состоянии. Это также устраняет проблему для поддержания состояний нескольких общих объектов в критическом разделе, например. список сотрудников и список фотографий сотрудников.

Кроме того, этот метод также полезен, когда вам нужно получить блокировку на примитивных типах, например, int или decimal и т.д.

По-моему, если вы используете эту технику, как предложили все остальные, вам не нужно дважды выполнять нулевую проверку. например в принятом ответе Cris использовал, если условие дважды, что действительно не имеет никакого значения, потому что заблокированный объект отличается от того, что на самом деле изменяется, если вы блокируете другой объект, тогда выполнение первой нулевой проверки бесполезно и трата процессора,

Я бы предложил следующий фрагмент кода:

object readonly syncRootEmployee = new object();

List<Employee> employeeList = null;
List<EmployeePhoto> employeePhotoList = null;

public void AddEmployee(Employee employee, List<EmployeePhoto> photos)
{
    lock (syncRootEmployee)
    {
        if (employeeList == null)
        {
            employeeList = new List<Employee>();
        }

        if (employeePhotoList == null)
        {
            employeePhotoList = new List<EmployeePhoto>();
        }

        employeeList.Add(employee);
        foreach(EmployeePhoto ep in photos)
        {
            employeePhotoList.Add(ep);
        }
    }
}

Я не вижу никакого состояния гонки здесь, если кто-либо еще увидит состояние гонки, пожалуйста, ответьте в комментариях. Как вы можете видеть в приведенном выше коде, он разрешает сразу 3 проблемы, один нулевой проверки не требуется перед блокировкой, второй создает критический раздел без блокировки двух общих источников, а третья блокировка более одного объекта вызывает взаимоблокировки из-за отсутствия внимания при записи код.

Вот как я использую блокировки для примитивных типов.

object readonly syncRootIteration = new object();

long iterationCount = 0;
long iterationTimeMs = 0;

public void IncrementIterationCount(long timeTook)
{
    lock (syncRootIteration)
    {
        iterationCount++;
        iterationTimeMs = timeTook;
    }
}

public long GetIterationAvgTimeMs()
{
    long result = 0;

    //if read without lock the result might not be accurate
    lock (syncRootIteration)
    {
        if (this.iterationCount > 0)
        {
            result = this.iterationTimeMs / this.iterationCount;
        }
    }

    return result;
}

Счастливые потоки:)