Может быть volatile в определяемых пользователем типах, чтобы облегчить написание ориентированного на многопотоковое исполнение кода.

Я знаю, что в нескольких вопросах/ответах ранее было совершенно ясно, что volatile связано с видимым состоянием модели памяти С++, а не с многопоточностью.

С другой стороны, в этой статье автор Александреску использует ключевое слово volatile не во время выполнения. функция, а скорее как проверка времени компиляции, чтобы заставить компилятор не принять код, который может быть не потокобезопасным. В статье ключевое слово используется скорее как тег required_thread_safety, чем как фактическое предполагаемое использование volatile.

Уместно ли это (не)использование volatile? Какие возможные ошибки могут быть скрыты в подходе?

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

Основное упрощение статьи:

Если вы объявите переменную volatile, для нее можно будет вызвать только volatile методов-членов, поэтому компилятор заблокирует вызов кода для других методов. Объявление экземпляра std::vector как volatile заблокирует все виды использования класса. Добавление оболочки в виде указателя блокировки, выполняющего const_cast для освобождения от требования volatile, позволяет разрешить любой доступ через указатель блокировки.

Украл из статьи:

template <typename T>
class LockingPtr {
public:
   // Constructors/destructors
   LockingPtr(volatile T& obj, Mutex& mtx)
      : pObj_(const_cast<T*>(&obj)), pMtx_(&mtx)
   { mtx.Lock(); }
   ~LockingPtr()   { pMtx_->Unlock(); }
   // Pointer behavior
   T& operator*()  { return *pObj_; }
   T* operator->() { return pObj_; }
private:
   T* pObj_;
   Mutex* pMtx_;
   LockingPtr(const LockingPtr&);
   LockingPtr& operator=(const LockingPtr&);
};

class SyncBuf {
public:
   void Thread1() {
      LockingPtr<BufT> lpBuf(buffer_, mtx_);
      BufT::iterator i = lpBuf->begin();
      for (; i != lpBuf->end(); ++i) {
         // ... use *i ...
      }
   }
   void Thread2();
private:
   typedef vector<char> BufT;
   volatile BufT buffer_;
   Mutex mtx_; // controls access to buffer_
};

ПРИМЕЧАНИЕ

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

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

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


person Community    schedule 22.03.2010    source источник
comment
Они обсуждают этот код в настоящее время в comp.lang.c++.moderated.   -  person Johannes Schaub - litb    schedule 22.03.2010
comment
@Johannes: Это обсуждение, которое они должны проводить дважды в десятилетие? Я помню бурные дискуссии, которые возникли, когда Андрей опубликовал эту статью. Как и здесь, большая часть накала была вызвана тем, что люди просто читали volatile и темы в одной и той же статье и прыгали на нее, даже не пытаясь понять, в чем заключается идея.   -  person sbi    schedule 05.04.2011


Ответы (8)


Я думаю, что проблема не в потокобезопасности, обеспечиваемой volatile. Это не так, и в статье Андрея об этом не говорится. Здесь для этого используется mutex. Вопрос в том, является ли использование volatilekeyword для обеспечения статической проверки типов наряду с использованием мьютекса для потокобезопасного кода злоупотреблением ключевым словом volatile? ИМХО, это довольно умно, но я встречал разработчиков, которые не являются поклонниками строгой проверки типов только ради этого.

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

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

Но если вы пурист, который верит в дух C++, известный как строгая проверка типов; это хорошая альтернатива.

person Community    schedule 22.03.2010
comment
+1. Я не согласен с дополнительным уровнем косвенного дискомфорта. Любой, кто занимается многопоточностью, наверняка знает, что блокировка мьютекса — это дорогостоящая операция. Дополнительный уровень косвенности почти ничего не даст по сравнению с самой блокировкой. - person David Rodríguez - dribeas; 22.03.2010
comment
@dribeas: я имел в виду синтаксический дискомфорт. Поверьте мне, некоторым разработчикам не нравится писать обернутый код вместо естественного для глаз простого прямого кода. - person Abhay; 22.03.2010

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

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

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

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

Интересно, есть ли что-нибудь, что можно сказать о компиляторах, предоставляющих дополнительные модификаторы типа в стиле const через атрибуты. Страуструп говорит: "Рекомендуется использовать атрибуты контролировать только то, что не влияет на смысл программы, но может помочь обнаружить ошибки». Если бы вы могли заменить все упоминания volatile в коде на [[__typemodifier(needslocking)]], то, думаю, было бы лучше. Тогда было бы невозможно использовать объект без const_cast, и, надеюсь, вы не написали бы const_cast, не подумав о том, что именно вы отбрасываете.

person Community    schedule 22.03.2010
comment
В этом ответе есть несколько интересных жемчужин: в зависимости от частичного решения вы, вероятно, забудете о случаях, когда это не поможет. Другим хорошим является потенциальное использование атрибутов (даже если я не вижу, как на самом деле разыграть трюк только с атрибутами, это потребует некоторого типа attribute_cast). Я думал о классах, которые имеют volatile функций-членов, но не видел ни одной из них в реальном коде, и мне стало интересно, насколько далеко может зайти проблема. Спасибо - person David Rodríguez - dribeas; 22.03.2010
comment
+1. Он взламывает систему типов C++, чтобы придать новое значение volatile. В качестве примечания: отбрасывание volatile, как в LockingPtr(volatile T& obj, Mutex& mtx) : pObj_(const_cast<T*>(&obj)), pMtx_(&mtx), является неопределенным поведением в соответствии с разделом 6.7.3(5) стандарта, не так ли? - person stephan; 22.03.2010
comment
@dribeas: ну, в определении [[__typemodifier(X)]], которое я только что придумал в своей голове, const_cast может удалить любой модификатор типа, в том числе придуманный пользователем, точно так же, как в настоящее время он удаляет const и volatile. Поскольку определяемые пользователем модификаторы типов не будут иметь никакого значения в языке, кроме предотвращения несовместимых присваиваний, это безопасно, если вы не используете результат таким образом, который нарушает инварианты, подразумеваемые модификатором в этой программе. Я не подвергал эту идею тщательному рецензированию и даже не думал об этом дольше, чем потребовалось, чтобы напечатать ;-) - person Steve Jessop; 22.03.2010
comment
@stephan: возможно, но Александреску писал в 2001 году. Сейчас 2010 год, а использование потоков вообще все еще является неопределенным поведением (мы надеемся, что это ненадолго!). Вы зависите от своей реализации для этого материала. Возможно, в его статье это должно было быть рассмотрено должным образом, но я могу простить, что он пропустил это, не говоря ни слова. - person Steve Jessop; 22.03.2010
comment
@Steve: использование потоков не является неопределенным поведением. Стандарт ничего не говорит о потоках, а не о том, что они не определены. Большая разница. - person John Dibling; 22.03.2010
comment
@John: никакой разницы со стандартным POV. Расширения компилятора не определены стандартом. Реализация определяет поведение потоков, хотя иногда и не с той детализацией и точностью, с которой стандарт определяет поведение. С другой стороны, реализация может (и часто так и делает) определять поведение разыменования нулевого указателя, и тем не менее мы все равно говорим, что поведение такого кода не определено. - person Steve Jessop; 23.03.2010
comment
@Steve: потоки не являются расширениями компилятора больше, чем диалоговые окна. Это службы, предоставляемые операционной системой. - person John Dibling; 23.03.2010
comment
Это не правда. Потоки — это не просто библиотека. Они требуют поддержки компилятора во всем коде, который работает в многопоточном режиме, например, чтобы гарантировать, что доступ к памяти не переупорядочивается через барьер памяти каким-либо преобразованием компилятора. Это историческая роль volatile: она предотвращает изменение порядка доступа при вызовах неизвестного кода. Диалоговые окна не требуют, чтобы весь код в приложении был скомпилирован со специальными ограничениями на поведение (по крайней мере, не модальные или полностью асинхронные — диалоговые окна с обратными вызовами make из других потоков — это, конечно, другое дело). - person Steve Jessop; 23.03.2010
comment
А, нашел статью, на которую хотел сослаться в первую очередь: hpl.hp.com/techreports/2004/HPL-2004-209.html - person Steve Jessop; 23.03.2010
comment
он предотвращает изменение порядка доступа при вызовах неизвестного кода. Извините, это не совсем правильно, volatile предотвращает изменение порядка доступа между другими и предотвращает запись в объект значений, которые не отображаются в коде (например, использование памяти в качестве временного хранилища для что-то еще, если компилятор знает, что это будет безоговорочно перезаписано перед следующим чтением), так что это часть одного из возможных путей к потокобезопасной модели памяти в C. Использование таких трюков с вызовами неизвестного кода в любом случае в любом случае предотвращается из-за возможность алиасинга. - person Steve Jessop; 23.03.2010

C++03 §7.1.5.1p7:

Если предпринимается попытка сослаться на объект, определенный с типом с уточнением volatile, посредством использования lvalue с типом с уточнением volatile, поведение программы не определено.

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

template<class T>
struct Lock;

template<class T, class Mutex>
struct Volatile {
  Volatile() : _data () {}
  Volatile(T const &data) : _data (data) {}

  T        volatile& operator*()        { return _data; }
  T const  volatile& operator*() const  { return _data; }

  T        volatile* operator->()        { return &**this; }
  T const  volatile* operator->() const  { return &**this; }

private:
  T _data;
  Mutex _mutex;

  friend class Lock<T>;
};

Дружба нужна для жесткого контроля энергонезависимого доступа через уже заблокированный объект:

template<class T>
struct Lock {
  Lock(Volatile<T> &data) : _data (data) { _data._mutex.lock(); }
  ~Lock() { _data._mutex.unlock(); }

  T& operator*() { return _data._data; }
  T* operator->() { return &**this; }

private:
  Volatile<T> &_data;
};

Пример:

struct Something {
  void action() volatile;  // Does action in a thread-safe way.
  void action();  // May assume only one thread has access to the object.
  int n;
};
Volatile<Something> data;
void example() {
  data->action();  // Calls volatile action.
  Lock<Something> locked (data);
  locked->action();  // Calls non-volatile action.
}

Есть два предостережения. Во-первых, вы по-прежнему можете обращаться к общедоступным элементам данных (Something::n), но они будут квалифицированы как volatile; это, вероятно, потерпит неудачу в разных точках. И, во-вторых, Что-то не знает, действительно ли оно было определено как volatile, и отбрасывание этого volatile (из «этого» или из членов) в методах все равно будет UB, если оно было определено таким образом:

Something volatile v;
v.action();  // Compiles, but is UB if action casts away volatile internally.

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

person Community    schedule 02.02.2011

Основываясь на другого кода и полностью устраняя необходимость в спецификаторе volatile, это не только работает, но и правильно распространяет const (аналогично iterator vs const_iterator). К сожалению, для двух типов интерфейса требуется довольно много стандартного кода, но вам не нужно повторять какую-либо логику методов: каждый из них по-прежнему определяется один раз, даже если вам нужно аналогичным образом «дублировать» «изменчивые» версии. к обычной перегрузке методов на константные и неконстантные.

#include <cassert>
#include <iostream>

struct ExampleMutex {  // Purely for the sake of this example.
  ExampleMutex() : _locked (false) {}
  bool try_lock() {
    if (_locked) return false;
    _locked = true;
    return true;
  }
  void lock() {
    bool acquired = try_lock();
    assert(acquired);
  }
  void unlock() {
    assert(_locked);
    _locked = false;
  }
private:
  bool _locked;
};

// Customization point so these don't have to be implemented as nested types:
template<class T>
struct VolatileTraits {
  typedef typename T::VolatileInterface       Interface;
  typedef typename T::VolatileConstInterface  ConstInterface;
};

template<class T>
class Lock;
template<class T>
class ConstLock;

template<class T, class Mutex=ExampleMutex>
struct Volatile {
  typedef typename VolatileTraits<T>::Interface       Interface;
  typedef typename VolatileTraits<T>::ConstInterface  ConstInterface;

  Volatile() : _data () {}
  Volatile(T const &data) : _data (data) {}

  Interface       operator*()        { return _data; }
  ConstInterface  operator*() const  { return _data; }
  Interface       operator->()        { return _data; }
  ConstInterface  operator->() const  { return _data; }

private:
  T _data;
  mutable Mutex _mutex;

  friend class Lock<T>;
  friend class ConstLock<T>;
};

template<class T>
struct Lock {
  Lock(Volatile<T> &data) : _data (data) { _data._mutex.lock(); }
  ~Lock() { _data._mutex.unlock(); }

  T& operator*() { return _data._data; }
  T* operator->() { return &**this; }

private:
  Volatile<T> &_data;
};

template<class T>
struct ConstLock {
  ConstLock(Volatile<T> const &data) : _data (data) { _data._mutex.lock(); }
  ~ConstLock() { _data._mutex.unlock(); }

  T const& operator*() { return _data._data; }
  T const* operator->() { return &**this; }

private:
  Volatile<T> const &_data;
};

struct Something {
  class VolatileConstInterface;
  struct VolatileInterface {
    // A bit of boilerplate:
    VolatileInterface(Something &x) : base (&x) {}
    VolatileInterface const* operator->() const { return this; }

    void action() const {
      base->_do("in a thread-safe way");
    }

  private:
    Something *base;

    friend class VolatileConstInterface;
  };

  struct VolatileConstInterface {
    // A bit of boilerplate:
    VolatileConstInterface(Something const &x) : base (&x) {}
    VolatileConstInterface(VolatileInterface x) : base (x.base) {}
    VolatileConstInterface const* operator->() const { return this; }

    void action() const {
      base->_do("in a thread-safe way to a const object");
    }

  private:
    Something const *base;
  };

  void action() {
    _do("knowing only one thread accesses this object");
  }

  void action() const {
    _do("knowing only one thread accesses this const object");
  }

private:
  void _do(char const *restriction) const {
    std::cout << "do action " << restriction << '\n';
  }
};

int main() {
  Volatile<Something> x;
  Volatile<Something> const c;

  x->action();
  c->action();

  {
    Lock<Something> locked (x);
    locked->action();
  }

  {
    ConstLock<Something> locked (x);  // ConstLock from non-const object
    locked->action();
  }

  {
    ConstLock<Something> locked (c);
    locked->action();
  }

  return 0;
}

Сравните class Something с тем, что потребовало бы использование volatile Александреску:

struct Something {
  void action() volatile {
    _do("in a thread-safe way");
  }

  void action() const volatile {
    _do("in a thread-safe way to a const object");
  }

  void action() {
    _do("knowing only one thread accesses this object");
  }

  void action() const {
    _do("knowing only one thread accesses this const object");
  }

private:
  void _do(char const *restriction) const volatile {
    std::cout << "do action " << restriction << '\n';
  }
};
person Community    schedule 02.02.2011

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

const int cv = 123;
int* that = const_cast<int*>(&cv);
*that = 42;

... это вызывает неопределенное поведение в соответствии со стандартом, но на практике что-то произойдет. Возможно, значение будет изменено. Возможно, будет сигфоул. Может быть, запустится авиасимулятор — кто знает. Дело в том, что вы не знаете независимо от платформы, что произойдет. Итак, очевидное обещание const не выполнено. Значение может быть, а может и не быть константой.

Теперь, учитывая, что это правда, является ли использование const злоупотреблением языком? Конечно, нет. Это по-прежнему инструмент, который язык предоставляет, чтобы помочь вам писать лучший код. Он никогда не будет универсальным инструментом, обеспечивающим неизменность значений — в конечном счете этим инструментом является мозг программиста, — но делает ли это const бесполезным?

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

То же самое относится и к volatile. Объявление чего-либо изменчивым не сделает вашу программу потокобезопасной. Вероятно, это даже не сделает эту переменную или объект потокобезопасным. Но компилятор будет применять семантику CV-квалификации, и осторожный программист может использовать этот факт, чтобы помочь ему написать более качественный код, помогая компилятору определить места, где он может написать ошибку. Так же, как компилятор помогает ему, когда он пытается это сделать:

const int cv = 123;
cv = 42;  // ERROR - compiler complains that the programmer is potentially making a mistake

Забудьте о границах памяти и атомарности изменчивых объектов и переменных, точно так же, как вы давно забыли об истинной константности cv. Но используйте инструменты, которые дает вам язык, чтобы писать лучший код. Одним из таких инструментов является volatile.

person Community    schedule 22.03.2010

Вам лучше этого не делать. volatile даже не был изобретен для обеспечения потокобезопасности. Он был изобретен для правильного доступа к отображаемым в памяти аппаратным регистрам. Ключевое слово volatile не влияет на функцию внеочередного выполнения ЦП. Вы должны использовать правильные вызовы ОС или инструкции CAS, определенные ЦП, ограничения памяти и т. д.

CAS

Забор памяти

person Community    schedule 22.03.2010
comment
Просто взглянув на предоставленный код, я не вижу никакой выгоды, делающей BufT нестабильным. Вся эта штука с безопасностью потоков теперь является проблемой Mutex. Поскольку BufT является закрытым членом, лучше, чтобы он НЕ был нестабильным из соображений производительности. Но также я вижу, что есть проблема с вашим кодом. Как только Thread1 заблокирует Mutex, Thread2 никогда не получит доступ к BufT, поэтому он застрянет на первой строке своего тела... - person Malkocoglu; 22.03.2010
comment
Если вы внимательно прочитаете статью и код, то увидите, что используемая переменная не является volatile (квалификатор удаляется через const_cast). Это просто трюк времени компиляции. Вся безопасность потоков, как указывает @Malkocoglu, правильно обрабатывается с помощью мьютекса. Я думаю, путаница - большой недостаток подхода - person David Rodríguez - dribeas; 22.03.2010
comment
-1: я колебался, чтобы проголосовать против этого, но в конечном итоге я сделал это, потому что volatile не используется в статье или этом посте, чтобы гарантировать, что выполнение не по порядку происходит определенным образом. Он используется для использования системы типов компилятора, поэтому ваш пост не имеет отношения к вопросу. - person John Dibling; 22.03.2010
comment
Я ненавижу длинные вопросы с вводящим в заблуждение заголовком ;-) - person Malkocoglu; 26.03.2010
comment
Я изо всех сил старался не вводить в заблуждение, но трудно написать для этого правильное название. Приму предложения, если они у вас есть. - person David Rodríguez - dribeas; 02.02.2011

В статье ключевое слово используется скорее как тег required_thread_safety, чем как фактическое предполагаемое использование volatile.

Не прочитав статью — почему тогда Андрей не использует указанный тег required_thread_safety? Злоупотребление volatile здесь звучит не очень хорошо. Я считаю, что это вызывает больше путаницы (как вы сказали), а не избегает ее.

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

person Community    schedule 02.02.2011
comment
Причина использования volatile вместо тега заключается в том, что система типов может использовать его во время компиляции, чтобы пометить недопустимый доступ. Теперь, когда вы упомянули об этом, мне нужно подумать, может ли что-то, основанное на теге, работать... - person David Rodríguez - dribeas; 02.02.2011
comment
@David: я думаю, что это должно сработать, но я с готовностью признаю, что этого может быть труднее достичь. Во-первых, вам нужно реализовать интерфейс интеллектуальных указателей. Возможно. На самом деле не думал об этом. - person Konrad Rudolph; 02.02.2011

Я не знаю конкретно, разумен ли совет Александреску, но, несмотря на то, что я уважаю его как супер-умного чувака, его трактовка семантики volatile предполагает, что он вышел далеко за пределы своей области знаний. Volatile не имеет абсолютно никакой ценности в многопоточности (см. " rel="nofollow noreferrer">здесь для хорошего рассмотрения предмета), и поэтому утверждение Александреску о том, что volatile пригодно для многопоточного доступа, заставляет меня серьезно задуматься, насколько я могу доверять в остальной части его статьи.

person Community    schedule 22.03.2010
comment
Я думаю, вы неправильно поняли статью, единственная причина, по которой используется ключевое слово volatile, заключается в том, что оно подразумевает во время компиляции, а не во время выполнения. Во время выполнения все использования (которые компилируются) удаляют квалификатор volatile перед операцией с экземпляром: в скомпилированном коде нет volatile, так как все использования проходят через указатель const_casted внутри LockPtr, который является энергонезависимым. - person David Rodríguez - dribeas; 22.03.2010
comment
Из статьи, на которую вы ссылаетесь, Ганс Бем указывает, что существует только три переносных варианта использования volatile. Ханс Бем ошибается (хотя и не в том смысле, в котором ему должно быть стыдно) — Александреску представил четвертое использование volatile, которое заключается в том, чтобы полагаться на его заразительное поведение в стиле const, а не на его семантику, связанную с доступом к памяти. - person Steve Jessop; 22.03.2010
comment
@David: статья оставляет мало места для интерпретации: она предназначена для использования в сочетании с переменными, доступ к которым и их изменение осуществляются в разных потоках. По сути, без volatile либо становится невозможным написание многопоточных программ, либо компилятор тратит впустую огромные возможности оптимизации. Я понимаю, что основной смысл статьи заключается в использовании volatile в качестве своего рода ортогонального const-квалификатора. Я никогда не отказывался от этого. Я просто выразил сомнения по поводу статьи о volatile, написанной кем-то, кто явно не понял ее первоначального замысла. - person Marcelo Cantos; 22.03.2010
comment
Александреску смешивает две вещи: то, для чего определено volatile, и как volatile на самом деле использовалось разработчиками компиляторов (в частности, он упоминает MS), добавляя многопоточность в C и C++. Многопоточность не может быть реализована просто как библиотека, она требует языковой поддержки, и, по крайней мере, в Windows часть этой языковой поддержки появилась в виде дополнительной семантики для volatile. С когерентным кешем доступ к энергозависимой памяти примерно такой же, как барьер памяти. Я не знаю, то ли Александреску размыл это различие по незнанию, то ли ради простоты. - person Steve Jessop; 22.03.2010