Защищен ли доступ к полям VIA `ref` параметров параметрами операторов блокировки в методе CALLED? - PullRequest
0 голосов
/ 19 сентября 2018

Дано,

private object _x;

private object LoadAndSet(ref object x) {
   // lock established over read and update
   lock (somePrivateObjectNotUsedElsewhereThatIsIrrelvantToTheQuestion) {
       if (x == null)
           x = Load();
       return x;
   }
}

Вызывается как,

public object Load() {
    return LoadAndSet(ref _x);
}
  • Является ли Чтение / Запись в Поле (_x) "передано по ссылке", охватываемой гарантиями атомарности / видимости lock?

То есть, является ли первый код эквивалентным следующему, где поле используется непосредственно?(Назначение происходит напрямую, а не через параметр ref.)

private object _x;

private object LoadAndSetFieldDirectly() {
   // lock established over read and update
   lock (somePrivateObjectNotUsedElsewhereThatIsIrrelvantToTheQuestion) {
       if (_x == null)
           _x = Load();
       return _x;
   }
}

public object Load() {
    return LoadAndSetFieldDirectly();
}

Я подозреваю, что это правда из-за использования ldind.ref и stind.ref в MSIL метода;тем не менее, вопрос заключается в том, чтобы при написании такого ref кода.

запрашивать авторитетную документацию / информацию о безопасности потоков (или ее отсутствии).

1 Ответ

0 голосов
/ 19 сентября 2018

Семантика lock(lockObject) { statements }:

  • В области заблокированного кода, заблокированного одним и тем же экземпляром lockObject, никогда не было двух отдельных потоков.Если один поток находится в регионе, то он либо войдет в бесконечный цикл, завершится нормально или сработает.(Или, для опытных игроков, переходит в состояние ожидания через Monitor.Wait; это не предназначено для обучения правильному использованию объекта монитора.) Второй поток не войдет в защищенную область, пока элемент управления не покинет область.
  • Считывание переменных во время или после блокировки не будет перемещено «назад во времени» до до блокировки.
  • Записывает переменные во время или до того, как блокировка не будет перемещена вперед во времени"после блокировки.

(Это краткое неформальное резюме; точные сведения о том, что спецификация C # гарантирует при переупорядочении операций чтения и записи, см. в спецификации.)

Эта семантика применяется во время выполнения независимо от того, являются ли переменные, к которым обращаются в теле блокировки, полями, локальными, нормальными формальными параметрами, формальными параметрами ref / out, элементами массива или разыменованием указателя .

Тем не менее, три вещи заставляют меня нервничать по поводу вашего кода.

Во-первых, это ненужное и неоптимальное переосмыслениесуществующий механизм.Если вы хотите реализовать отложенную инициализацию, используйте Lazy<T>.Зачем отказываться от своего и рискнуть ошибиться, если вы можете использовать код, написанный экспертами, которые уже извлекли из этого каждый бит производительности?

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

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

...