Вот поле класса,
class someClass {
Int someClassField = nil
...
(пожалуйста, пожалуйста (!) игнорируйте проблемы видимости, этот вопрос касается общего дизайна, а не языковой реализации). Если я смотрю онлайн-руководства, мне говорят, что это поле безопасно для использования несколькими потоками. Когда в учебниках говорится «безопасно», они не означают, что один поток не может мешать значению, видимому для другого. Такие помехи могут быть намерением — поле может быть счетчиком. Что означают учебники, так это то, что когда один поток изменяет это поле, поле не останется в небезопасном состоянии. Возьми это поле,
class someClass {
List<List> someClassField = new List<Int>()
...
Как я понимаю, если поле представляет собой простой список, один поток может оставить его в несогласованном состоянии (т.е. частично отключен). Если другой поток использует список, он потерпит неудачу — на языке, подобном C, это будет катастрофой. Даже чтение может потерпеть неудачу.
Итак, класс, используемый в поле, можно попросить скопировать его состояние (копирование может быть расширено до полной защиты неизменности, но я сохраняю обсуждение простым). Если класс копирует свое состояние, то изменения выполняются вне копии в поле, в новой копии, измененной для возврата. Эта новая измененная копия может быть переназначена на поле. Но является ли это назначение потокобезопасным — в том смысле, что значение поля не может находиться в несовместимом состоянии — потому что распределение ссылки нового объекта на поле является атомарным?
Я игнорирую все проблемы, связанные с тем, может ли языковой движок переупорядочивать, кэшировать и т. Д. См. Много постов ниже (особенно Java, кажется),
Я хотел бы проработать этот вопрос в меньшем масштабе …
В большинстве языков присвоение объекта является атомарным.
В этом конкретном случае вы должны быть осторожны, так как x=new X()
на всех языках нет гарантии, что X полностью инициализирован перед присваиванием. Я не уверен, где C # стоит на этом.
Вы также должны учитывать видимость и атомарность. Например, в Java вам нужно будет сделать переменную volatile, так как иначе изменения, сделанные в одном потоке, могут вообще не быть видимыми в другом потоке.
C ++ определяет гонку данных как два или более потоков, потенциально обращающихся к одной и той же области памяти одновременно, по крайней мере, один из которых является модификацией. Поведение программ с гонками данных не определено. Поэтому нет, нескольким потокам небезопасно получать доступ к этому полю, если хотя бы один из них может его изменить.
Запись ссылки на Java является атомарной (запись в long или double выполняется только в том случае, если поле изменчиво между прочим), но это само по себе совсем не помогает.
Пример для демонстрации:
class Foo {
int x;
public Foo() { x = 5};
}
Теперь предположим, что мы делаем назначение, такое как foo = new Foo()
(без окончательных или изменчивых модификаторов для foo!). С точки зрения низкого уровня, это означает, что мы должны сделать следующее:
но пока конструктор не читает поле, которому мы его присваиваем, компилятор может делать следующее:
Резьбонарезной безопасно? Конечно, нет (и вы никогда не увидите обновления, если не вставите барьеры памяти). Java дает больше гарантий, когда используются конечные поля, поэтому создание нового неизменяемого объекта будет поточно-ориентированным (вы никогда не увидите неинициализированное значение конечного поля). Изменчивые поля (здесь речь идет о присваивании, а не полях в объекте), также позволяют избежать этой проблемы как в Java, так и в C #. Не уверен насчет C # и только для чтения.
В Java присвоение ссылкам и примитивам является атомарным, за исключением 64-битных типов примитивов. long
а также double
, Назначения на Java long
с и double
можно сделать атомарными, объявив их с volatile
модификатор. Увидеть: Являются ли 64-битные назначения в Java атомарными на 32-битной машине?
Это так, потому что спецификация Java VM требует этого для того, чтобы VM была Java-совместимой.
Scala работает поверх стандартной виртуальной машины Java и поэтому предоставляет те же гарантии, что и Java, в отношении назначений, если только они не начнут использовать JNI.
Одна из проблем с C / C ++ (и одна из его сильных сторон) заключается в том, что оба языка позволяют очень тонко отображать структуры данных на адреса памяти. На этом уровне то, являются ли записи в память атомарными или нет, сильно зависит от аппаратной платформы. Например, процессоры обычно не могут атомарно считывать, не говоря уже о записи в переменные, которые не выровнены соответствующим образом. например Когда 16-битные переменные не выровнены по четным адресам, или когда 32-битные переменные не выровнены по адресам, кратным 4, и так далее. Ситуация ухудшается, когда переменная выходит за пределы одной строки кэша в следующую или за одну страницу в следующую. Следовательно, C не гарантирует, что назначения будут атомарными.