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

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

Я пытался изменить переменную lock a Boolean, когда обнаружил следующую ошибку:

'bool' не является ссылочным типом, как требуется оператором блокировки

Кажется, что в операторах lock разрешены только ссылочные типы, но я не уверен, что понимаю почему.

Андреас заявляет в своем комментарии:

Когда объект [тип значения] передается из одного потока в другой, выполняется копия, поэтому потоки работают на 2 разных объекта, что безопасно.

Это правда? Означает ли это, что когда я делаю следующее, я фактически изменяю два разных x в методах xToTrue и xToFalse?

public static class Program {

    public static Boolean x = false;

    [STAThread]
    static void Main(string[] args) {

        var t = new Thread(() => xToTrue());
        t.Start();
        // ...
        xToFalse();
    }

    private static void xToTrue() {
        Program.x = true;
    }

    private static void xToFalse() {
        Program.x = false;
    }
}

(этот код сам по себе явно бесполезен в своем состоянии, это только для примера)


P.S: Я знаю об этом вопросе Как правильно заблокировать тип значения. Мой вопрос не связан с тем, как, но почему.

4b9b3361

Ответ 1

Просто дикая догадка здесь...

но если компилятор позволяет вам блокировать тип значения, вы в конечном итоге ничего не блокируете... потому что каждый раз, когда вы передавали тип значения в lock, вы передавали его в коробке; другая коробочная копия. Таким образом, блокировки будут такими, как если бы они были совершенно разными объектами. (так как они на самом деле)

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

Ответ 2

Вы не можете заблокировать тип значения, потому что он не имеет записи sync root.

Блокировка выполняется с помощью механизмов внутренней среды CLR и OS, которые полагаются на объект, имеющий запись, к которой можно получить доступ только одним потоком за один раз - синхронизировать корень блока. Любой ссылочный тип имел бы:

  • Указатель на тип
  • Синхронизировать корень блока
  • Указатель на данные экземпляра в куче

Ответ 3

Он расширяется до:

System.Threading.Monitor.Enter(x);
try {
   ...
}
finally {
   System.Threading.Monitor.Exit(x);
}

Хотя они будут компилироваться, Monitor.Enter/Exit требует ссылочного типа, потому что тип значения будет помещаться в отдельный экземпляр объекта каждый раз, чтобы каждый вызов Enter и Exit работал на разных объектах.

Из MSDN Ввести метод:

Используйте Monitor для блокировки объектов (то есть типов ссылок), а не типов значений. Когда вы передаете переменную типа значения Enter, она помещается в виде объекта. Если вы снова передаете одну и ту же переменную Enter, она помещается как отдельный объект, а поток не блокируется. В этом случае код, который предположительно защищает Монитор, не защищен. Кроме того, когда вы передаете переменную в Exit, создается еще один отдельный объект. Поскольку объект, переданный в Exit, отличается от объекта, переданного в Enter, Monitor выдает SynchronizationLockException. Для получения дополнительной информации см. Концептуальные темы "Мониторы".

Ответ 4

Если вы спросите концептуально, почему это не разрешено, я бы сказал, что ответ вытекает из того факта, что тип значения identity в точности эквивалентен его значению (что делает его типом значения).

Итак, кто-нибудь в мире, говорящий о int 4, говорит об одном и том же: как же вы можете потребовать эксклюзивный доступ к блокировке на нем?

Ответ 5

Мне было интересно, почему команда .Net решила ограничить разработчиков и позволить Monitor работать только с ссылками. Во-первых, вы считаете, что было бы удобно блокировать System.Int32 вместо определения выделенной переменной объекта только для целей блокировки, эти блокировки обычно ничего не делают.

Но тогда кажется, что любая функция, предоставляемая языком, должна иметь сильную семантику, которая не только будет полезной для разработчиков. Таким образом, семантика со значениями типов заключается в том, что всякий раз, когда тип значения появляется в коде, его выражение оценивается значением. Итак, с семантической точки зрения, если мы будем писать `lock (x) ', а x - тип примитивного значения, то он будет таким же, как мы сказали бы: "блокировать блок критического кода, агастировать значение переменной x", которая звучит больше чем странно, наверняка:). Между тем, когда мы встречаем переменные ref в коде, мы привыкли думать "О, это ссылка на объект" и подразумевать, что эта ссылка может быть разделена между блоками кода, методами, классами и даже потоками и процессами и, следовательно, может служить охранник.

В двух словах переменные типа значения отображаются в коде только для оценки их фактического значения в каждом выражении - не более.

Я предполагаю, что это один из основных моментов.

Ответ 6

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

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

Возможно, это сработает (хотя это совершенно бессмысленно и я не пробовал)

int x = 7;
object boxed = (object)x;

//thread1:
lock (boxed){
 ...
}
//thread2:
lock(boxed){
...
}

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

Что касается вашего второго вопроса - Нет, значение не копируется для каждого потока. Оба потока будут использовать одно и то же логическое значение, но нити не гарантируют, что он увидит самое свежее значение для него (когда один поток устанавливает значение, которое он не может сразу записать в ячейку памяти, так что любой другой поток, считывающий значение, получит "старый" результат).

Ответ 7

Из MSDN берется следующее:

Операторы блокировки (С#) и SyncLock (Visual Basic) могут использоваться для обеспечения того, чтобы блок кода выполнялся до завершения без прерывания другими потоками. Это достигается путем получения блокировки взаимного исключения для данного объекта на протяжении всего кода кода.

и

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

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

Ответ 8

В соответствии с этим MSDN Thread изменения в ссылочной переменной могут быть недоступны для всех потоков, и они могут закончиться использованием устаревших значений, и AFAIK. Я думаю, что типы значений делают копию, когда они передаются между потоками.

Чтобы точно указать из MSDN

Также важно уточнить, что факт присвоения является атомарным не означает, что запись сразу замечается другими потоки. Если ссылка не является изменчивой, то это возможно для другой поток, чтобы прочитать устаревшее значение из ссылки некоторое время после того, как ваш поток обновил его. Однако само обновление гарантированно быть атомарным (вы не увидите часть лежащей в основе указатель получает обновление).

Ответ 9

Я думаю, что это один из тех случаев, когда ответ на вопрос почему "потому что инженер Microsoft реализовал его таким образом".

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

Почему Microsoft выбрала этот странный способ делать то, о чем все догадываются. Они могли бы сделать Monitor для класса, который вы должны были создать. Я уверен, что видел статью сотрудника MS, которая сказала, что при отражении этот шаблон дизайна был ошибкой, но я не могу сейчас его найти.