Это тонкая проблема, требующая тщательного анализа.
Во-первых, код, заданный в вопросе, не имеет смысла, поскольку он проверяет локальную переменную, которая гарантированно не будет нулевой. Предположительно реальный код читает из нелокальной переменной, которая может быть или не быть нулевой, и может быть изменена в нескольких потоках.
Это очень опасная позиция, и я настоятельно не рекомендую вам следовать этому архитектурному решению . Найдите другой способ поделиться памятью между рабочими.
Чтобы ответить на ваш вопрос:
Первая версия вопроса: у оператора ?.
та же семантика, что и у вашей версии, где вы вводите временную строку?
Да, это так. Но мы еще не закончили.
Второй вопрос, который вы не задавали, заключается в следующем: возможно ли, что компилятор C #, джиттер или ЦП приводят к тому, что версия с временным вводит дополнительное чтение? То есть мы гарантируем, что
var tempList = someListThatCouldBeNull;
if (tempList != null)
tempList.Add(new object());
никогда не выполняется , как если бы вы написали
var tempList = someListThatCouldBeNull;
if (tempList != null)
someListThatCouldBeNull.Add(new object());
Вопрос о «введенных чтениях» сложен в C #, но короткая версия такова: вообще говоря, можно предположить, что чтения не будут введены таким образом.
Мы хороши? Конечно, нет. Код не является поточно-ориентированным, поскольку Add
может вызываться в нескольких потоках, что является неопределенным поведением!
Предположим, мы это исправим. Все хорошо сейчас?
Нет. Нам все еще не следует доверять этому коду.
Почему бы и нет?
На оригинальном плакате не показан механизм, который гарантирует, что текущее значение someListThatCouldBeNull
читается. Доступ к нему осуществляется с помощью блокировки? Это изменчиво? Введены ли барьеры памяти? Спецификация C # очень ясно показывает тот факт, что чтения могут быть произвольно перемещены назад во времени, если нет никаких специальных эффектов, таких как блокировки или летучие компоненты. Возможно, вы читаете кэшированное значение.
Точно так же мы не видели код, который выполняет запись; эти записи могут быть произвольно перенесены в будущее. Любая комбинация чтения, перенесенного в прошлое, или записи, перенесенной в будущее, может привести к считыванию «устаревшего» значения.
Теперь предположим, что мы решили эту проблему. Это решает всю проблему? Конечно, нет. Мы не знаем, сколько потоков задействовано или какие-либо из этих потоков также читают связанные переменные, и есть ли какие-либо предполагаемые ограничения порядка для этих чтений . C # не требует наличия глобально согласованного представления порядка всех операций чтения и записи! Два потока могут не соглашаться в том порядке, в котором выполнялись операции чтения и записи в переменные. То есть, если модель памяти допускает два возможных наблюдаемых порядка, для одного потока допустимо наблюдать один, а для другого потока - другой. Если логика вашей программы неявно зависит от единственного наблюдаемого порядка чтения и записи, значит, ваша программа ошибочна .
Теперь, возможно, вы понимаете, почему я настоятельно советую не делиться памятью таким образом. Это минное поле тонких жуков.
Так что же вам делать?
- Если вы можете: прекратить использование потоков . Найдите другой способ справиться с асинхронностью.
- Если вы не можете сделать это, использует потоки в качестве рабочих, которые решают проблему, а затем возвращаются в пул . Трудно понять, что два потока одновременно работают с одной и той же памятью. Отключить один поток, вычислить что-то и вернуть значение, когда это будет сделано, гораздо проще понять, и вы можете ...
- ... используйте параллельную библиотеку задач или другой инструмент, предназначенный для правильного управления связью между потоками.
- Если вы не можете этого сделать, попытайтесь изменить как можно меньше переменных .Не устанавливайте переменную в нуль.Если вы заполняете список, инициализируйте список потокобезопасным списком типа один раз, а затем читайте только из этой переменной.Пусть объект списка обрабатывает проблемы с потоками для вас.