views:

251

answers:

6

I have a class that is shared between several projects, some uses of it are single-threaded and some are multi-threaded. The single-threaded users don't want the overhead of mutex locking, and the multi-threaded users don't want to do their own locking and want to be able to optionally run in "single-threaded mode." So I would like to be able to select between real and "dummy" mutexes at runtime.

Ideally, I would have a shared_ptr<something> and assign either a real or fake mutex object. I would then "lock" this without regard to what's in it.

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

Now there is a signals2::dummy_mutex but it does not share a common base class with boost::mutex.

So, what's an elegant way to select between a real mutex and a dummy mutex (either the one in signals2 or something else) without making the lock/guard code more complicated than the example above?

And, before you point out the alternatives:

  • I could select an implementation at compile time, but preprocessor macros are ugly and maintaining project configurations is painful for us.
  • Users of the class in a multi-threaded environment do not want to take on the responsibility of locking the use of the class rather than having the class do its own locking internally.
  • There are too many APIs and existing usages involved for a "thread-safe wrapper" to be a practical solution.
A: 

Is this not sufficient?

   class SomeClass
    {
    public:
        SomeClass(void);
        ~SomeClass(void);
        void Work(bool isMultiThreaded = false)
        {
            if(isMultiThreaded)
           {
               lock // mutex lock ...
               {
                    DoSomething
               }
           }
           else
           {
                DoSomething();
           }
       }   
    };
Jagannath
I already have a large number of places using the RAII locking pattern I described. Changing them all to use this kind of verbose wrapper is just not practical.
Tim Sylvester
+4  A: 

How about something like this? Its untested but should be close to OK. You might consider making the template class hold a value rather than a pointer if your mutexes support the right kinds of constructions. Otherwise you could specialise the MyMutex class to get value behaviour.

Also it's not being careful about copying or destruction .. I leave that as an exercise to the reader ;) ( shared_ptr or storing a value rather than a pointer should fix this)

Oh and the code would be nicer using RAII rather than explicit lock/unlock... but that's a different question.I assume thats what the unique_lock in your code does?

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

}
Michael Anderson
Add a try_lock() method and it will conform to the Lockable concept for use with the unique_lock class. See: http://www.boost.org/doc/libs/1_41_0/doc/html/thread/synchronization.html#thread.synchronization.mutex_concepts.lockable
Jason Govig
@Jason Thx updated to do that.
Michael Anderson
+1. This is basically "external polymorphism" as described here: http://www.cs.wustl.edu/~schmidt/PDF/External-Polymorphism.pdf.
Void
It does look like this will allow me to leave the existing locks unchanged. I will look at implementing something like this. Thanks.
Tim Sylvester
+2  A: 

Since the two mutex classes signals2::dummy_mutex and boost::mutex don't share a common base class you could use something like "external polymorphism" to allow to them to be treated polymorphically. You'd then use them as locking strategies for a common mutex/lock interface. This allows you to avoid using "if" statements in the lock implementation.

NOTE: This is basically what Michael's proposed solution implements. I'd suggest going with his answer.

Void
A: 

In general, a mutex is only needed if the resource is shared between multiple processes. If an instance of the object is unique for a (possibly multi-threaded) process, then a Critical Section is often more appropriate.

In Windows, the single-threaded implementation of a Critical Section is a dummy one. Not sure what platform you are using.

Michael J
+1  A: 

Have you ever heard about Policy-based Design ?

You can define a Lock Policy interface, and the user may choose which policy she wishes. For ease of use, the "default" policy is precised using a compile-time variable.

#ifndef PROJECT_DEFAULT_LOCK_POLICY
#define PROJECT_DEFAULT_LOCK_POLICY TrueLock
#endif

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

This way, your users can choose their policies with a simple compile-time switch, and may override it one instance at a time ;)

Matthieu M.
That would definitely work, and it's how the existing boost "dummy_mutex" class is used in signals2. Unfortunately I do not want to make the class into a template and move all its code to the header file, I'm trying to minimize the scope of the change on existing code.
Tim Sylvester
A: 

Just FYI, here's the implementation I ended up with.

I did away with the abstract base class, merging it with the no-op "dummy" implementation. Also note the shared_ptr-derived class with an implicit conversion operator. A little too tricky, I think, but it lets me use shared_ptr<IMutex> objects where I previously used boost::mutex objects with zero changes.

header file:

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

implementation file:

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);
}
Tim Sylvester