Почему летучих не достаточно? - PullRequest
9 голосов
/ 05 февраля 2009

Я в замешательстве. Ответы на мой предыдущий вопрос , кажется, подтверждают мои предположения. Но как указано здесь volatile недостаточно для обеспечения атомарности в .Net. Либо такие операции, как инкремент и присваивание в MSIL, не транслируются напрямую в один, собственный OPCODE, либо многие процессоры могут одновременно считывать и записывать в одну и ту же область ОЗУ.

Для уточнения:

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

Ответы [ 7 ]

10 голосов
/ 05 февраля 2009

Херб Саттер недавно написал статью о volatile и что это на самом деле означает (как это влияет на порядок доступа к памяти и атомарность) в нативном C ++. .NET и Java среды. Это очень хорошее чтение:

6 голосов
/ 05 февраля 2009

volatile в .NET делает доступ к переменной atomic.

Проблема в том, что этого часто недостаточно. Что делать, если вам нужно прочитать переменную, и если она равна 0 (указывает на то, что ресурс свободен), вы устанавливаете его на 1 (указывает, что он заблокирован, и другие потоки должны держаться от него подальше).

Чтение 0 является атомным. Написание 1 является атомным. Но между этими двумя операциями может произойти все что угодно. Вы можете прочитать 0, а затем, прежде чем вы сможете написать 1, другой поток подключается, читает 0 и записывает 1.

Однако, volatile в .NET делает гарантией атомарности доступа к переменной. Это просто не гарантирует безопасность потоков для операций, основанных на множественном доступе к нему. (Отказ от ответственности: volatile в C / C ++ даже не гарантирует этого. Просто, чтобы вы знали. Он намного слабее и иногда является источником ошибок, потому что люди предполагают, что это гарантирует атомарность:))

Так что вам также нужно использовать блокировки, чтобы объединить несколько операций в один потокобезопасный блок. (Или, для простых операций, операции Interlocked в .NET могут помочь)

5 голосов
/ 05 февраля 2009

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

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

Другое - волатильность, когда ожидается изменение этого значения и почему.

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

Однако, вторая проблема, даже когда вы делаете блокировку, что увидят другие потоки.

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

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

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

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

Блокировки решают многошаговые операции, изменчивость обрабатывает, как компилятор кэширует значение поля в регистре, и вместе они решат больше проблем.

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

Например, если вы используете 32-разрядный процессор и записываете 64-разрядное значение, для этой операции записи потребуется два шага, и если другой поток в другом процессоре сможет прочитать 64-разрядное значение до завершения шага 2 он получит половину предыдущего значения и половину нового, приятно смешанного вместе, что может быть даже хуже, чем получить устаревшее значение.


Редактировать : Чтобы ответить на комментарий, volatile гарантирует атомарность операции чтения / записи, это хорошо, правда, в некотором смысле, поскольку ключевое слово volatile нельзя применять к полям которые больше, чем 32-битные, в результате чего команда с одним процессором поля может считываться / записываться как на 32-, так и на 64-битных процессорах. И да, это предотвратит максимальное сохранение значения в регистре.

Таким образом, часть комментария неверна, volatile нельзя применить к 64-битным значениям.

Обратите внимание, что volatile имеет некоторую семантику относительно переупорядочения операций чтения / записи.

Для получения соответствующей информации см. документацию MSDN или C # спецификацию , найдено здесь , раздел 10.5.3.

1 голос
/ 05 февраля 2009

На аппаратном уровне несколько ЦП никогда не могут одновременно выполнять одновременную запись в одно и то же расположение атомарной памяти. Размер атомарной операции чтения / записи зависит от архитектуры ЦП, но обычно составляет 1, 2 или 4 байта в 32-битной архитектуре. Однако, если вы попытаетесь прочитать результат обратно, всегда есть вероятность, что другой ЦП сделал запись в то же место ОЗУ между ними. На низком уровне спин-блокировки обычно используются для синхронизации доступа к общей памяти. На языке высокого уровня такие механизмы можно назвать, например, критические регионы.

Тип volatile просто гарантирует, что переменная сразу же записывается в память при ее изменении (даже если значение будет использоваться в той же функции). Компилятор, как правило, будет хранить значение во внутреннем регистре как можно дольше, если это значение будет использоваться позже в той же функции, и оно сохраняется в ОЗУ, когда все изменения завершены или когда функция возвращается. Изменчивые типы в основном полезны при записи в аппаратные регистры или когда вы хотите быть уверены, что значение сохраняется в ОЗУ, например, в. многопоточная система.

0 голосов
/ 05 февраля 2009

Проблема возникает с сохраненными в регистре копированными значениями вашей переменной.

При считывании значения процессор сначала проверяет, находится ли он в регистре (быстро), прежде чем проверять основную память (медленнее).

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

Если вы ищете записи одного операционного кода, вам нужно использовать методы, связанные с Interlocked.Increment. Но они довольно ограничены в том, что они могут сделать в одной безопасной инструкции.

Самая безопасная и надежная ставка - блокировка () (если вы не можете сделать блокировку. *)

Редактировать: Запись и чтение являются атомарными, если они находятся в блокированном или заблокированном операторе. Одного летучего недостаточно по условиям вашего вопроса

0 голосов
/ 05 февраля 2009

Ваш вопрос не совсем понятен, потому что volatile указывает, как происходит чтение , а не атомарность многошаговых процессов. Моя машина тоже не косит мой газон, но я стараюсь не сопротивляться этому. :)

0 голосов
/ 05 февраля 2009

Volatile - это ключевое слово компилятора, которое сообщает компилятору, что делать. Это не обязательно переводит в (по существу) операции шины, которые требуются для атомарности. Это обычно остается за операционной системой.

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

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