Безопасна ли установка копий из неизменяемых файлов от повреждения потока?

0

Вот поле класса,

class someClass {
  Int someClassField = nil
  ...

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

class someClass {
  List<List> someClassField = new List<Int>()
  ...

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

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

Я игнорирую все проблемы, связанные с тем, что языковой движок может переупорядочивать, кэшировать и т.д. См. Множество сообщений ниже (особенно Java, похоже)

Я хотел бы заняться этим вопросом в меньших масштабах...

  • 3
    Я не думаю, что этот вопрос имеет большой смысл без ссылки на конкретный язык.
  • 0
    Кажется, есть согласие - концептуально, да, но у языковых реализаций будут проблемы волатильности или никаких гарантий атомарности. Спасибо всем за попытку рассуждать о том, что «не сделано».
Показать ещё 5 комментариев
Теги:
multithreading

4 ответа

1

Запись ссылки в Java является атомарной (записи в longs или doubles только в том случае, если поле является volatile btw), но это само по себе не помогает.

Пример для демонстрации:

class Foo {
     int x;
     public Foo() { x = 5};
}

Предположим, что мы выполняем задание, например foo = new Foo() (без конечных или изменчивых модификаторов для foo!). С точки зрения низкого уровня это означает, что мы должны сделать следующее:

  1. выделять память
  2. запустите конструктор
  3. назначьте адрес памяти в поле.

но до тех пор, пока конструктор не прочитает поле, которому мы его присваиваем, компилятор также может сделать следующее:

  1. выделять память
  2. назначьте адрес памяти в поле.
  3. запустите конструктор

Резьбонарезной безопасно? Конечно, нет (и вы никогда не гарантируете, что увидите обновление, если не поместите барьеры памяти). Java дает больше гарантий, когда задействованы конечные поля, поэтому создание нового неизменяемого объекта будет потокобезопасным (вы никогда не увидите неинициализированное значение конечного поля). Неустойчивые поля (речь идет о присвоении здесь не полей в объекте), чтобы избежать этой проблемы и в java, и в С#. Не уверен, что С# и только для чтения.

1

В Java назначение ссылок и примитивов является атомарным, за исключением 64-битных примитивных типов long и double. Назначения Java long и double могут быть сделаны атомарными, объявив их с volatile модификатором. См.: 64-разрядные назначения в Java-атоме на 32-битной машине?

Это связано с тем, что спецификация Java VM требует его для того, чтобы виртуальная машина была совместимой с Java.

Scala работает поверх стандартной Java VM и поэтому также будет предоставлять те же гарантии, что и Java, в отношении присвоений, если они не начнут использовать JNI.

Одной из проблем с C/C++ (и одной из его сильных сторон) является то, что оба языка позволяют очень мелкозернизованное отображение структур данных на адреса памяти. На этом уровне, независимо от того, являются ли записи в память атомарными или не сильно зависят от аппаратной платформы. Например, CPU, как правило, не может считывать атомарно, не говоря уже о записи в переменные, которые не выровнены соответствующим образом. например, когда 16-битные переменные не совпадают с четными адресами или когда 32-битные переменные не выровнены по адресам, кратным 4, и так далее. Ухудшается, когда переменная выходит за пределы одной строки кэша в следующую или за одну страницу в другую. Следовательно, C не гарантирует, что присвоения будут атомарными.

  • 0
    Спасибо за примечание о C. Я надеялся, что могут быть некоторые межъязыковые вклады.
1

В большинстве языков назначение объектов является атомарным.

В этом конкретном случае вам нужно быть осторожным, хотя, выполняя x=new X() на всех языках нет гарантии, что X полностью инициализируется до назначения. Я не уверен, где С# стоит на этом.

Вы также должны учитывать видимость, а также атомарность. В Java, например, вам нужно будет сделать переменную volatile, поскольку в противном случае изменения, сделанные в одном потоке, могут вообще не отображаться в другом потоке.

1

C++ определяет гонку данных в виде двух или более потоков, потенциально доступных одновременно для одного и того же места памяти, по меньшей мере одно из которых является модификацией. Поведение программ с расами данных не определено. Таким образом, нет, для нескольких потоков небезопасно обращаться к этому полю, если хотя бы один из них может его изменить.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню