Май, энергозависимый быть в определяемых пользователем типах для помощи пишущему ориентированному на многопотоковое исполнение коду

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

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

Это (ab) использование volatile соответствующий? Какие возможные глюки могут быть скрыты в подходе?

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

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

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

Кража от статьи:

template 
class LockingPtr {
public:
   // Constructors/destructors
   LockingPtr(volatile T& obj, Mutex& mtx)
      : pObj_(const_cast(&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 lpBuf(buffer_, mtx_);
      BufT::iterator i = lpBuf->begin();
      for (; i != lpBuf->end(); ++i) {
         // ... use *i ...
      }
   }
   void Thread2();
private:
   typedef vector BufT;
   volatile BufT buffer_;
   Mutex mtx_; // controls access to buffer_
};

Примечание:

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

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

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

25
задан 3 revs 22 March 2010 в 12:13
поделиться

5 ответов

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

CAS

Memory Fence

0
ответ дан 28 November 2019 в 21:58
поделиться

Думаю, проблема не в безопасности потоков, обеспечиваемой volatile . Нет, и в статье Андрея ничего не сказано. Здесь для этого используется мьютекс . Проблема заключается в том, является ли использование ключевого слова volatile для обеспечения статической проверки типов вместе с использованием мьютекса для поточно-безопасного кода злоупотреблением volatile ключевое слово? ИМХО это довольно умно, но я встречал разработчиков, которые не любили строгую проверку типов просто ради этого.

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

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

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

6
ответ дан 28 November 2019 в 21:58
поделиться

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

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

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

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

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

4
ответ дан 28 November 2019 в 21:58
поделиться

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

-2
ответ дан 28 November 2019 в 21:58
поделиться

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

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

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

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

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

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

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

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

1
ответ дан 28 November 2019 в 21:58
поделиться
Другие вопросы по тегам:

Похожие вопросы: