Должны ли мьютексы быть изменяемыми? - PullRequest
53 голосов
/ 08 ноября 2010

Не уверен, если это вопрос стиля или что-то с жестким правилом ...

Если я хочу сохранить интерфейс public-метода как можно более постоянным, но сделать поток объекта безопасным,Я использую изменяемые мьютексы?В целом, это хороший стиль или предпочтительный интерфейс неконстантного метода?Пожалуйста, обоснуйте свою точку зрения.

Ответы [ 2 ]

51 голосов
/ 09 ноября 2010

Скрытый вопрос: куда вы помещаете мьютекс, защищающий ваш класс?

В качестве резюме, скажем, вы хотите прочитать содержимое объекта, который защищен мьютексом.

Метод «read» должен быть семантически «const», потому что он не изменяет сам объект.Но чтобы прочитать значение, вам нужно заблокировать мьютекс, извлечь его, а затем разблокировать мьютекс, то есть сам мьютекс должен быть изменен, то есть сам мьютекс не может быть «константным».

Еслимьютекс внешний

Тогда все в порядке.Объект может быть «const», и мьютекс не должен быть:

Mutex mutex ;

int foo(const Object & object)
{
   Lock<Mutex> lock(mutex) ;
   return object.read() ;
}

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

Я знаю: я стал жертвой этой проблемы.

Если мьютекс является внутренним

В целях инкапсуляции вы должны располагать мьютекс как можно ближе к объекту, который он защищает.

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

Хороший способ сделать это - получить исходныйобъект с наследующим шаблоном, добавляющий функцию мьютекса:

template <typename T>
class Mutexed : public T
{
   public :
      Mutexed() : T() {}
      // etc.

      void lock()   { this->m_mutex.lock() ; }
      void unlock() { this->m_mutex.unlock() ; } ;

   private :
      Mutex m_mutex ;
}

Таким образом, вы можете написать:

int foo(const Mutexed<Object> & object)
{
   Lock<Mutexed<Object> > lock(object) ;
   return object.read() ;
}

Проблема в том, что он не будет работать, потому что object является постоянными объект блокировки вызывает неконстантные методы lock и unlock.

Дилемма

Если вы считаете, что const ограничено побитовыми объектами const, то вы 'перевернут и должен вернуться к «решению внешнего мьютекса».

Решение состоит в том, чтобы признать, что const является более семантическим классификатором (как и volatile при использовании в качестве квалификатора метода классов).Вы скрываете тот факт, что класс не является полностью const, но все же убедитесь, что предоставили реализацию, которая держит обещание, что значимые части класса не будут изменены при вызове const метода.

Затем вы должны объявить свой изменяемый мьютекс и методы блокировки / разблокировки const:

template <typename T>
class Mutexed : public T
{
   public :
      Mutexed() : T() {}
      // etc.

      void lock()   const { this->m_mutex.lock() ; }
      void unlock() const { this->m_mutex.unlock() ; } ;

   private :
      mutable Mutex m_mutex ;
}

Внутреннее решение мьютекса является хорошим ИМХО: наличие объектов, объявленных один рядом с другим в одной руке, ис другой стороны, объединение их обоих в обертку - это то же самое.

Но у агрегации есть следующие плюсы:

  1. Это более естественно (вы блокируетеобъект до доступа к нему)
  2. Один объект, один мьютекс.Поскольку стиль кода заставляет вас следовать этому шаблону, он снижает риски взаимоблокировки, поскольку один мьютекс будет защищать только один объект (а не несколько объектов, которые вы на самом деле не помните), и один объект будет защищен только одним мьютексом (а немножественный мьютекс, который необходимо заблокировать в правильном порядке)
  3. Приведенный выше класс мьютекса можно использовать для любого класса

Так что держите свой мьютекс как можно ближе к объекту мьютекса(например, используя вышеупомянутую конструкцию Mutexed), и перейдите к квалификатору mutable для мьютекса.

Edit 2013-01-04

Очевидно, у Херба Саттера есть та же точка зрения: его представлениео "новых" значениях const и mutable в C ++ 11 очень поучительно:

http://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/

28 голосов
/ 08 ноября 2010

[ Отредактированный ответ ]

Хорошей идеей является использование методов const с изменяемыми мьютексами (кстати, не возвращайте ссылки, обязательно возвращайте по значению), по крайней мере,чтобы указать, что они не модифицируют объект.Мьютексы не должны быть const, было бы бесстыдной ложью определять методы блокировки / разблокировки как const ...

На самом деле это (и памятка) - единственное добросовестное использование ключевого слова mutable.1008 *

Вы также можете использовать мьютекс, который является внешним по отношению к вашему объекту: позаботьтесь о том, чтобы все ваши методы были реентерабельными, и чтобы пользователь сам управлял блокировкой: { lock locker(the_mutex); obj.foo(); } не так сложно набрать, а

{
    lock locker(the_mutex);
    obj.foo();
    obj.bar(42);
    ...
}

обладает тем преимуществом, что не требует двух блокировок мьютекса (и вам гарантировано, что состояние объекта не изменилось).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...