Являются ли энергозависимые операции чтения и записи атомарными в 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 не будет выдавать атомарные операции для изменчивых переменных, их нужно явно использовать).