Являются ли энергозависимые операции чтения и записи атомарными в Windows+VisualC?

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

Теперь, стандартный ответ C(++), очевидно, нет.

Однако в компиляторе Windows & Visual C++ ситуация кажется не очень ясной.

Я недавно ответил и процитировал официальные документы MSDN на volatile

Microsoft Specific

Объекты, объявленные как volatile, являются (...)

  • Запись в энергозависимый объект (volatile write) имеет семантику Release; ссылка на глобальный или статический объект ? это происходит до того, как запись в энергозависимый объект в последовательности команд произойдет до того, как запись в энергозависимый файл в скомпилированном двоичном файле.
  • Чтение летучего объекта (volatile read) имеет семантику Acquire; ссылка на глобальный или статический объект ? это происходит после чтения энергозависимой памяти в последовательности команд после того, как энергозависимое чтение в скомпилированном двоичном файле.

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

[акцент мой]

Теперь, читая это, мне кажется, что переменная переменная будет обрабатываться компилятором MS как std::atomic будет в следующем стандарте C++11.

Однако в комментарии к моему ответу пользователь Hans Passant написал: "Эта статья на MSDN очень неудачная, она совершенно неправильная. Вы не можете реализовать блокировку с помощью volatile, даже с версией Microsoft. (...)"


Пожалуйста, обратите внимание: пример, приведенный в MSDN, выглядит довольно подозрительно, так как вы не можете вообще реализовать блокировку без атомарного обмена. (Как также указал Алекс.) Это все еще оставляет вопрос в отношении. действительность другой информации, приведенной в этой статье MSDN, особенно для случаев использования, таких как здесь и здесь.)


Кроме того, есть документы для функций Interlocked*, особенно InterlockedExchange with принимает переменную volatile(!?) и выполняет атомарное чтение + запись. (Обратите внимание, что один вопрос, который у нас есть по SO - когда следует использовать InterlockedExchange? - не дает официального ответа, нужна ли эта функция для атомарного доступа только для чтения или только для записи.)

Более того, volatile Приведенные выше документы как-то ссылаются на "глобальный или статический объект", где я бы подумал, что "настоящая" семантика получения / выпуска должна применяться ко всем значениям.

Вернуться к вопросу

В Windows, с Visual C++ (2005 - 2010), переменная (32bit? Int?) Будет объявлена ​​как volatile разрешить атомарное чтение и запись в эту переменную - или нет?

Что особенно важно для меня, так это то, что это должно выполняться (или нет) в Windows/VC++ независимо от процессора или платформы, на которой работает программа. (То есть, имеет ли значение WinXP/32-битная или Windows 2008R2/64-битная, работающая на Itanum2?)

Пожалуйста, подкрепите свой ответ проверяемой информацией, ссылками, тест-кейсами!

5 ответов

Решение

Да, они атомарны в Windows/vC++ (если вы соответствуете требованиям выравнивания и т. Д. Или конечно)

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

В противном случае нет способа проверить блокировку и заявить о ней в одной неделимой операции.

РЕДАКТИРОВАТЬ: Как прокомментировано ниже, все выровненные обращения к памяти на x86 32-битной или ниже в любом случае являются атомарными. Ключевым моментом является то, что volatile делает доступ к памяти упорядоченным. (Спасибо за указание на это в комментариях)

Начиная с Visual C++ 2005 переменные переменные являются атомарными. Но это относится только к этому конкретному классу компиляторов и к платформам x86/AMD64. Например, PowerPC может изменить порядок чтения / записи памяти и потребует барьеров для чтения / записи. Я не знаю, какова семантика для компиляторов gcc-класса, но в любом случае использование volatile для атомарности не очень переносимо.

см. первое замечание "Специфично для Microsoft": http://msdn.microsoft.com/en-us/library/12a04hfd%28VS.80%29.aspx

Немного не по теме, но все равно поехали.

... есть документы для функций Interlocked*, особенно InterlockedExchange, который принимает переменную (!)...

Если вы думаете об этом:

void foo(int volatile*);

Это говорит:

  • аргумент должен быть указателем на переменную типа int или
  • аргумент также может быть указателем на переменную типа int?

Последний является правильным ответом, поскольку функция может передавать оба указателя на volatile и non-volatile int.

Отсюда тот факт, что InterlockedExchangeX() аргумент volatile-qualified не подразумевает, что он должен работать только с volatile целыми числами.

Дело в том, чтобы, вероятно, позволить такие вещи, как

singleton& get_instance()
{
    static volatile singleton* instance;
    static mutex instance_mutex;

    if (!instance)
    {
        raii_lock lock(instance_mutex);

        if (!instance) instance = new singleton;
    }

    return *instance;
}

который сломался бы, если бы instance было записано до завершения инициализации. С семантикой MSVC вы гарантируете, что, как только увидите instance != 0объект завершен, будучи инициализированным (что не имеет место без надлежащей семантики барьера, даже с традиционной изменчивой семантикой).

Эта двойная проверка (анти) шаблон блокировки на самом деле довольно распространена и нарушается, если вы не предоставляете семантику барьера. Однако, если есть гарантии, что доступ к volatile переменные - это барьеры на приобретение + освобождение, тогда это работает.

Не полагайтесь на такую ​​пользовательскую семантику volatile хоть. Я подозреваю, что это было введено, чтобы не сломать существующие кодовые базы. Ни в коем случае не пишите блокировки в соответствии с примером MSDN. Это, вероятно, не работает (я сомневаюсь, что вы можете написать блокировку, используя только барьер: для этого вам нужны атомарные операции - CAS, TAS и т. Д.).

Единственный переносимый способ написания шаблона блокировки с двойной проверкой - это использование C++ 0x, который предоставляет подходящую модель памяти и явные барьеры.

Под x86 эти операции гарантированно будут атомарными без необходимости использования инструкций на основе LOCK, таких как Interlocked* (см. руководства разработчика Intel 3A, раздел 8.1):

Основные операции с памятью всегда будут выполняться атомарно:

• Чтение или запись байта

• Чтение или запись слова, выровненного по 16-битной границе

• Чтение или запись двойного слова, выровненного по 32-битной границе

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

• Чтение или запись четырех слов, выровненных по 64-битной границе

• 16-битный доступ к не кэшированным областям памяти, которые вписываются в 32-битную шину данных

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

• Нераспределенные 16-, 32- и 64-битные обращения к кешируемой памяти, которые помещаются в строку кеша

Это означает volatile будет использоваться только для предотвращения кэширования и переупорядочения команд компилятором (MSVC не будет выдавать атомарные операции для изменчивых переменных, их нужно явно использовать).

Другие вопросы по тегам