Почему мы не можем заблокировать тип значения? - PullRequest
51 голосов
/ 25 ноября 2011

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

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

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

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

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

Это правда?Означает ли это, что когда я делаю следующее, я на самом деле изменяю два разных 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;
    }
}

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


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

Ответы [ 9 ]

42 голосов
/ 25 ноября 2011

Здесь просто дикая догадка ...

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

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

26 голосов
/ 28 ноября 2011

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

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

  • Указатель на тип
  • Синхронизация блока root
  • Указатель на данные экземпляра в куче
18 голосов
/ 25 ноября 2011

Расширяется до:

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

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

Из MSDN Введите метод Страница:

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

6 голосов
/ 07 ноября 2013

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

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

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

Полагаю, это один из главных моментов.

5 голосов
/ 25 ноября 2011

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

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

2 голосов
/ 25 ноября 2011

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

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

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

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

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

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

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

1 голос
/ 25 ноября 2011

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

Точно цитата из MSDN

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

1 голос
/ 25 ноября 2011

Следующее взято из MSDN:

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

и

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

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

0 голосов
/ 25 ноября 2011

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...