2009-02-05 2 views
9

Я смущен. Ответы на мои previous question, похоже, подтверждают мои предположения. Но, как указано here, волатильность недостаточна для обеспечения атомарности в .Net. Либо такие операции, как инкремент и присвоение в MSIL, не переводится непосредственно в один, собственный OPCODE или многие процессоры могут одновременно читать и записывать в то же место RAM.Почему волатильности недостаточно?

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

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

ответ

10

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

+3

Это * очень хорошее чтение (и, безусловно, заслуживает +1), но обычно предпочитают, чтобы ответы SO были несколько самодостаточными. Было бы неплохо, если бы ваш ответ содержал краткое изложение важных частей этой статьи, так что (1) люди, просматривающие страницу, увидят это, и (2), поэтому ответ остается актуальным, если статья перемещена или снята. – jalf

-1

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

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

+0

duh. но как насчет прироста и назначения? –

+0

Если вы говорите о взаимосвязанных операциях, то есть гарантированных атомных операциях, эти два должны быть реализованы либо с помощью операций шины (x86 использует модификатор кода блокировки), либо OS (для выполнения эквивалента) необходимо использовать lwarx/stwrx. – MSN

+0

Зачем нужна шинная операция для атомарности, особенно если ваш процессор является одним из Core2Duo? И ушел в ОС? Ты наверное шутишь! –

6

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

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

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

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

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

+0

Обратите внимание, что он не является изменчивым, что гарантирует атомарность переменных - это наоборот: если к переменной можно обращаться атомарно, к ней может применяться «volatile». – xxbbcc

0

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

0

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

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

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

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

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

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

5

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

+0

Просто, чтобы быть ясным, ваше описание летучих звуков больше похоже на то, что было найдено на C/C++. В .net волатильность гарантирует атомарность, а также заставляет ее хранить в памяти, а не в регистре. Даже при больших значениях, например, 64-битных. – jalf

+0

@jalf: как вы определяете volatile по 64-битным значениям в C# ??? –

1

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

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

Смежные вопросы