58 votes

Les mutex devraient-ils être mutables?

Je ne sais pas si c'est une question de style ou quelque chose qui a une règle dure ...

Si je veux garder l'interface de méthode publique aussi constante que possible, mais sécuriser le thread d'objet, dois-je utiliser des mutex mutables? En général, est-ce un bon style ou faut-il privilégier une interface de méthode non const? S'il vous plaît justifier votre point de vue.

55voto

paercebal Points 38526

La question cachée est: où est-ce que le mutex la protection de votre classe?

En résumé, disons que vous souhaitez lire le contenu d'un objet qui est protégée par un mutex.

La "lecture" de la méthode doit être sémantiquement "const" car il ne modifie pas l'objet lui-même. Mais à la lecture de la valeur, vous avez besoin de verrouiller un mutex, extraire la valeur, puis déverrouille le mutex, sens le mutex doit lui-même être modifié, ce qui signifie le mutex lui-même ne peut pas être "const".

Si le mutex est externe

Ensuite, tout est ok. L'objet peut être "const", et le mutex n'avez pas besoin de l'être:

Mutex mutex ;

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

À mon humble avis, c'est une mauvaise solution, car n'importe qui pourrait réutiliser le mutex pour protéger quelque chose d'autre. Vous y compris. En fait, vous trahir vous-même parce que, si votre code est assez complexe, vous aurez juste être confus au sujet de ce que le mutex est exactement protéger.

Je sais: j'ai été victime de ce problème.

Si le mutex est interne

Pour l'encapsulation, vous devez mettre le mutex aussi près que possible de l'objet, il est la protection.

Habituellement, vous allez écrire une classe avec un mutex à l'intérieur. Mais tôt ou tard, vous aurez besoin de protéger certaines complexe STL structure, ou quelque chose écrit par un autre sans mutex l'intérieur (ce qui est une bonne chose).

Une bonne façon de le faire est de calculer l'objet d'origine avec un modèle hériter d'ajouter le mutex fonctionnalité:

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 ;
}

De cette façon, vous pouvez écrire:

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

Le problème est qu'il ne fonctionne pas car object est const, et le verrou de l'objet est l'appel de la non-const lock et unlock méthodes.

Le Dilemme

Si vous croyez const est limitée au niveau du bit const objets, puis, vous êtes foutu, et doit revenir à la "externes mutex solution".

La solution est d'admettre const est plus d'une sémantique de qualificateur (qu'est - volatile lorsqu'il est utilisé comme une méthode de qualifier de classes). Vous êtes à cacher le fait que la classe n'est pas entièrement const mais encore assurez-vous de fournir une implémentation qui maintient la promesse que le des parties significatives de la classe ne sera pas modifié lors de l'appel d'un const méthode.

Vous devez alors déclarer votre mutex mutable, et le verrouillage/déverrouillage des méthodes d' 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 ;
}

L'interne mutex solution est bonne à mon humble avis: le fait d'Avoir des objets déclaré l'un près de l'autre dans une main, et de les avoir à la fois agrégées dans une enveloppe dans l'autre main, est la même chose à la fin.

Mais l'agrégation suivantes pour:

  1. C'est plus naturel (vous pouvez verrouiller l'objet avant d'y accéder)
  2. Un objet, un mutex. Comme le code de style vous oblige à suivre ce modèle, il diminue les risques de blocage en raison d'un mutex permettra de protéger un seul objet (et non pas plusieurs objets vous ne me souviens pas vraiment), et un seul objet sera protégée par un mutex (et non par de multiples mutex qui doit être verrouillé dans le bon ordre)
  3. Le mutexed classe ci-dessus peut être utilisé pour n'importe quelle classe

Alors, gardez votre mutex aussi près que possible de la mutexed objet (par exemple, à l'aide de la Mutexed construction ci-dessus), et aller de l' mutable qualificatif pour le mutex.

Edit 2013-01-04

Apparemment, Herb Sutter ont le même point de vue: Sa présentation sur le "nouveau" sens de l' const et mutable en C++11 est très éclairant:

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

32voto

Alexandre C. Points 31758

[Réponse édité]

Fondamentalement, à l'aide de méthodes const avec mutable mutex est une bonne idée (ne pas retourner des références par la voie, assurez-vous de retourner de la valeur), au moins pour indiquer qu'ils ne modifient pas l'objet. Mutex ne doit pas être const, ce serait un mensonge éhonté pour définir verrouillage/déverrouillage des méthodes const...

En fait cela (et memoization) sont la seule juste utilise je vois de l' mutable mot-clé.

Vous pouvez également utiliser un mutex qui est externe à votre objet: organiser toutes vos méthodes pour être réentrant, et demandez à l'utilisateur de gérer la serrure elle-même : { lock locker(the_mutex); obj.foo(); } n'est pas difficile à type de, et

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

a l'avantage de ne pas nécessiter deux mutex serrures (et vous avez une garantie de l'état de l'objet n'a pas changé).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X