Подтвердить что ты не робот

Должны ли мьютексы быть изменчивыми?

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

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

4b9b3361

Ответ 1

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

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

На самом деле это (и memoization) - единственное справедливое использование, которое я вижу в ключе mutable.

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

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

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

Ответ 2

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

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

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

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

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

Mutex mutex ;

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

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

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

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

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

Обычно вы будете писать класс с мьютексом внутри. Но рано или поздно вам понадобится защитить сложную структуру 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 является константой, а объект блокировки вызывает методы non-const lock и unlock.

Дилемма

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

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

Затем вы должны объявить mutex mutable и методы блокировки/разблокировки 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 ;
}

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

Но агрегация имеет следующие преимущества:

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

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

Редактировать 2013-01-04

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

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