2010-02-02 12 views
2

J'ai une classe partagée entre plusieurs projets, certains d'entre eux sont mono-thread et d'autres sont multi-thread. Les utilisateurs mono-thread ne veulent pas la surcharge du verrouillage mutex, et les utilisateurs multithread ne veulent pas faire leur propre verrouillage et veulent pouvoir s'exécuter en mode "single-threaded". Je voudrais donc pouvoir choisir entre les mutex réels et "muets" à l'exécution.Sélection du mutex ou du mutex fictif à l'exécution

Idéalement, j'aurais un shared_ptr<something> et j'attribuerais un objet mutex réel ou faux. Je voudrais alors "verrouiller" ceci sans tenir compte de ce qu'il y a dedans.

unique_lock<something> guard(*mutex); 
... critical section ... 

Maintenant il y a une signals2::dummy_mutex mais il ne partage pas une classe de base commune avec boost::mutex. Alors, quelle est la manière élégante de choisir entre un mutex réel et un mutex factice (celui de signals2 ou autre chose) sans rendre le code de verrouillage/garde plus compliqué que l'exemple ci-dessus?

Et, avant que vous soulignez les alternatives:

  • je pourrais sélectionner une mise en œuvre au moment de la compilation, mais les macros de préprocesseur sont laids et maintenir les configurations de projet est pénible pour nous.
  • Les utilisateurs de la classe dans un environnement multithread ne veulent pas prendre la responsabilité de verrouiller l'utilisation de la classe plutôt que de laisser la classe effectuer son propre verrouillage interne.
  • Il existe trop d'API et d'utilisations existantes pour qu'un «wrapper thread-safe» soit une solution pratique.

Répondre

4

Que diriez-vous de quelque chose comme ceci? Son non testé, mais devrait être proche de OK. Vous pouvez considérer que la classe de modèle contient une valeur plutôt qu'un pointeur si vos mutex prennent en charge les types de constructions appropriés. Sinon, vous pourriez spécialiser la classe MyMutex pour obtenir un comportement de valeur.

En outre, il est pas en faisant attention à la copie ou la destruction .. Je laisse cela comme un exercice au lecteur;) (shared_ptr ou le stockage d'une valeur plutôt qu'un pointeur doit résoudre ce problème)

Oh et le code serait plus agréable d'utiliser RAII plutôt que de verrouiller/déverrouiller explicitement ... mais c'est une question différente. Je suppose que c'est ce que fait l'unique_lock dans votre code?

struct IMutex 
{ 
    virtual ~IMutex(){} 
    virtual void lock()=0; 
    virtual bool try_lock()=0; 
    virtual void unlock()=0; 
}; 

template<typename T> 
class MyMutex : public IMutex 
{ 
    public: 
    MyMutex(T t) : t_(t) {} 
    void lock() { t_->lock(); } 
    bool try_lock() { return t_->try_lock(); } 
    void unlock() { t_->unlock(); } 
    protected: 
    T* t_; 
}; 

IMutex * createMutex() 
{ 
    if(isMultithreaded()) 
    { 
    return new MyMutex<boost::mutex>(new boost::mutex); 
    } 
    else 
    { 
    return new MyMutex<signal2::dummy_mutex>(new signal2::dummy_mutex); 
    } 
} 


int main() 
{ 
    IMutex * mutex = createMutex(); 
    ... 
    { 
    unique_lock<IMutex> guard(*mutex); 
    ... 
    } 

} 
+1

Ajoutez une méthode try_lock() et elle sera conforme au concept Lockable à utiliser avec la classe unique_lock. Voir: http://www.boost.org/doc/libs/1_41_0/doc/html/thread/synchronization.html#thread.synchronization.mutex_concepts.lockable –

+0

@Jason Thx mis à jour pour faire cela. –

+0

+1. C'est essentiellement un "polymorphisme externe" tel que décrit ici: http://www.cs.wustl.edu/~schmidt/PDF/External-Polymorphism.pdf. – Void

0

Est-ce que ce n'est pas suffisant?

class SomeClass 
    { 
    public: 
     SomeClass(void); 
     ~SomeClass(void); 
     void Work(bool isMultiThreaded = false) 
     { 
      if(isMultiThreaded) 
      { 
       lock // mutex lock ... 
       { 
        DoSomething 
       } 
      } 
      else 
      { 
       DoSomething(); 
      } 
     } 
    }; 
+0

J'ai déjà un grand nombre d'endroits en utilisant le modèle de verrouillage RAII I décrit . Les changer tous pour utiliser ce genre d'emballage verbeux n'est tout simplement pas pratique. –

3

Depuis les deux classes mutex signals2::dummy_mutex et boost::mutex ne partagent pas une classe de base commune, vous pouvez utiliser quelque chose comme « external polymorphism » pour leur permettre de d'être traités de façon polymorphique. Vous pouvez ensuite les utiliser pour verrouiller strategies pour une interface mutex/verrou commune. Cela vous permet d'éviter d'utiliser les instructions "if" dans l'implémentation de verrouillage.

NOTE: C'est fondamentalement ce que la solution proposée par Michael implémente. Je suggère d'aller avec sa réponse.

0

En général, un mutex n'est nécessaire que si la ressource est partagée entre plusieurs processus.Si une instance de l'objet est unique pour un processus (éventuellement multithread), une section critique est souvent plus appropriée.

Sous Windows, l'implémentation monothread d'une section critique est une fictive. Vous ne savez pas quelle plate-forme vous utilisez.

1

Avez-vous déjà entendu parler de Policy-based Design?

Vous pouvez définir une interface Lock Policy, et l'utilisateur peut choisir quelle politique il souhaite. Pour faciliter l'utilisation, la politique "par défaut" est précisée en utilisant une variable de compilation.

#ifndef PROJECT_DEFAULT_LOCK_POLICY 
#define PROJECT_DEFAULT_LOCK_POLICY TrueLock 
#endif 

template <class LP = PROJECT_DEFAULT_LOCK_POLICY> 
class MyClass {}; 

De cette façon, les utilisateurs peuvent choisir leurs politiques avec un simple interrupteur à temps la compilation, et peuvent la remplacer une instance à la fois;)

+0

Cela fonctionnerait certainement, et c'est ainsi que la classe boost "dummy_mutex" est utilisée dans signals2. Malheureusement, je ne veux pas faire de la classe un modèle et déplacer tout son code vers le fichier d'en-tête, j'essaie de minimiser la portée de la modification sur le code existant. –

0

Juste Pour votre information, voici la mise en œuvre j'ai fini avec. J'ai supprimé la classe de base abstraite, en la fusionnant avec l'implémentation «fictive» sans opération. Notez également la classe dérivée shared_ptr avec un opérateur de conversion implicite. Un peu trop compliqué, je pense, mais ça me permet d'utiliser des objets shared_ptr<IMutex> où j'ai précédemment utilisé boost::mutex objets avec zéro changement.

fichier d'en-tête:

class Foo { 
    ... 
private: 
    struct IMutex { 
     virtual ~IMutex()  { } 
     virtual void lock()  { } 
     virtual bool try_lock() { return true; } 
     virtual void unlock() { } 
    }; 
    template <typename T> struct MutexProxy; 

    struct MutexPtr : public boost::shared_ptr<IMutex> { 
     operator IMutex&() { return **this; } 
    }; 

    typedef boost::unique_lock<IMutex> MutexGuard; 

    mutable MutexPtr mutex; 
}; 

fichier de mise en œuvre:

template <typename T> 
struct Foo::MutexProxy : public IMutex { 
    virtual void lock()  { mutex.lock(); } 
    virtual bool try_lock() { return mutex.try_lock(); } 
    virtual void unlock() { mutex.unlock(); } 
private: 
    T mutex; 
}; 

Foo::Foo(...) { 
    mutex.reset(single_thread ? new IMutex : new MutexProxy<boost::mutex>); 
} 

Foo::Method() { 
    MutexGuard guard(mutex); 
} 
0

Ceci est ma solution:

std::unique_lock<std::mutex> lock = dummy ? 
    std::unique_lock<std::mutex>(mutex, std::defer_lock) : 
    std::unique_lock<std::mutex>(mutex);