Если volatile бесполезен для многопоточности, почему атомарные операции требуют указателей на изменчивые данные? - PullRequest
27 голосов
/ 18 июня 2011

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

Например, в Mac OS X у нас есть семейство функций OSAtomic:

SInt32 OSIncrementAtomic(volatile SInt32 *address);
SInt32 OSDrecrementAtomic(volatile SInt32 *address);
SInt32 OSAddAtomic(SInt32 amount, volatile SInt32 *address);
// ...

И похоже, что в Windows аналогично используется ключевое слово volatile для операций Interlocked:

LONG __cdecl InterlockedIncrement(__inout LONG volatile *Addend);
LONG __cdecl InterlockedDecrement(__inout LONG volatile *Addend);

Также кажется, что в C ++ 11 атомарные типы имеют методы с модификатором volatile, что должно как-то означать, что ключевое слово volatile имеет какую-то связь с атомарностью.

Итак, что мне не хватает? Почему производители ОС и разработчики стандартных библиотек настаивают на использовании ключевого слова volatile для многопоточности, если оно бесполезно?

Ответы [ 4 ]

22 голосов
/ 18 июня 2011

Volatile не бесполезен для совместного доступа несколькими потоками, просто этого не обязательно достаточно:

  • это не обязательно обеспечивает семантику барьера памяти, которая может потребоваться;
  • он не обеспечивает гарантий атомарного доступа (например, если размер летучего объекта больше, чем размер слова в памяти собственной платформы)

Кроме того, вы также должны заметить, что спецификатор volatile в аргументах указателя на API в вашем примере действительно только дает API возможность получать указатели на volatile объекты без жалоб - это не требует что указатели указывают на фактические volatile объекты. Стандарт позволяет автоматически преобразовывать неквалифицированный указатель в квалифицированный указатель. Автоматический переход в другую сторону (квалифицированный указатель на неквалифицированный) в стандарте не предусмотрен (компиляторы обычно допускают это, но выдают предупреждение).

Например, если InterlockedIncrement() был создан как:

LONG __cdecl InterlockedIncrement(__inout LONG *Addend);  // not `volatile*`

API все еще может быть реализован для правильной внутренней работы. Однако, если у пользователя был непостоянный объект, который он хотел бы передать API, приведение было бы необходимо, чтобы компилятор не выдавал предупреждение.

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

17 голосов
/ 18 июня 2011

Мне вдруг пришло в голову, что я просто неверно истолковал значение volatile*.Так же, как const* означает, что pointee не должен меняться, volatile* означает, что pointee не должен кэшироваться в регистре.Это дополнительное ограничение, которое может быть свободно добавлено: столько, сколько вы можете разыграть char* к const char*, вы можете разыграть int* к volatile int*.

Таким образом, применяя *Модификатор 1010 * для пуантинов просто гарантирует, что атомарные функции могут использоваться для уже volatile переменных.Для энергонезависимых переменных добавление классификатора бесплатно.Моя ошибка заключалась в том, что интерпретировать наличие ключевого слова в прототипах как стимул для его использования, а не как удобство для тех, кто его использует.

2 голосов
/ 18 июня 2011

C ++ 11 содержит атомы для volatile и не volatile переменных.

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

1 голос
/ 18 июня 2011

Что ж, ключевое слово 'volatile' гарантирует, что компилятор всегда загружает / сохраняет значение переменной из / в память каждый раз, когда переменная появляется в вашем коде.
Это предотвращает определенные оптимизации, например что значение просто загружается в регистр один раз, а затем используется несколько раз.
Это полезно, когда у вас есть несколько потоков, которые могут изменять «общие» переменные между потоками. Вы должны будете всегда загружать / сохранять значение из / в память, чтобы проверить его значение, которое могло быть изменено другим потоком. Если volatile не использовалось, другой поток, возможно, не записал новое значение в память (но поместил бы его в регистр, иначе могла бы произойти какая-то другая оптимизация), и первый поток не заметил бы никакого изменения значения.

В ваших случаях 'volatile SInt32 * address' сообщает компилятору, что память, на которую указывает адрес, может быть изменена любым источником. Отсюда необходимость атомарной операции.

...