View Full Version: C++ Classes

C++ Learning Community > C++ Tips > C++ Classes


Title: C++ Classes
Description: Design of the Mutex


myork - August 17, 2005 05:04 PM (GMT)
From the original Post.

Step 1: Design.
===============
1) A mutex is a resource. So we need to initialize it and destroy it.
2) It is a unique resource. So the class should fully contain and control it.
2a) Thus you can not make copy a mutex.
3) To prevent stupid programmers from misusing it the lock/unlock should be automatic.
3a) This is to prevent a programmer from locking a mutex and the forgetting to unlock it.
4) NB Attempting to acquire a lock on a mutex will suspend the thread until it gets the lock (Hence the aggressive approach in (3)).
5) We need to be able to test a mutex to see if it is already locked. Acquiring a lock on the mutex if it is not already locked, but otherwise indicating immediate failure. But still maintain the automatic nature as defined in step (3).
6) A lot of C implementations leave undefined what happens if the same thread try's to re-lock the mutex. In C++ we don’t like undefined behavior so we will define it. BUT the behavior we want can depend on the situation. So how the mutex handles this will depend on a policy.

6a) A policy is a term used when designing C++ libraries. Its meaning is not exact but basically you pass a policy object to the object that needs a decision made and it uses this policy at runtime. Now there are multiple ways to do this, but policies generally do not change dynamically at runtime so we don't generally use inheritance or interfaces to do this.

Please no replies.
If you have comments. Send me an e-mail.

myork - August 18, 2005 08:28 PM (GMT)
The easy part:
==========
Cover Points 1,2 in the design:

CODE

class MyMutex
{
   public:
        MyMutex()  { CON_MUTEX(mutex);}
       ~MyMutex()  { DEL_MUTEX(mutex);}

   private:
       MUTEX    mutex; // MUTEXT is a typedef of the unerlying C mutex object.
                       // The actual value will depend on your C lib pthread_mutex or windows mutex etc.


       MyMutex(const MyMutex& copy); // No Definition to stop copying.
       MyMutex& operator=(const MyMutex& copy);

       // We could go extreme and stop programmers getting a pointer to a mutex
       // But I don't see and real value in that.
       // If people really want to go out of their way to defeat somthing they will
       // always be able to do it. We are not trying to stop malisious attempts at
       // subversion we are just trying to protect ourselves from accidental missuse.
};

myork - August 18, 2005 08:39 PM (GMT)
The next part:

There are three basic operations on a mutex.

CODE

void MyMutex::lock();
               // Attempt to lock the mutex. If the mutex is already locked
               // the suspend the thread until the lock is release.
               // If the same thread has already locked the mutex the action performed
               // is defined by the current policy
void MyMutex::unlock();
               // If one or more threads are suspended waiting for the mutex pick one
               // according to the appropriate policy and activate it (giving it the lock).
               // otherwise simply unlock the mutex. NB Only the thread that locked
               // the mutex should be allowed to unlock it (probably).
bool MyMutex::tryLock(time_t x = 0);
               // Like lock. But if the mutex is already locked and is not released in time 't'
               // then return false. If the mutex was succesfully locked return true.



The trouble is that for every call to lock() or tryLock() we want to garantee that the user will call unlock().
How do we ensure this?

Back tomorrow with the answer.

myork - August 19, 2005 01:26 PM (GMT)
OK. Now we look at parts (3) and (4) of the design.
The difficult part of this design was to try and garantee symetric calls to lock/unlock.

Basically the easy way to do this is to make the calls private so that they can not be called directly. And then use the construction/destruction of another object to call the lock/unlock methods.

CODE
class MyMutex
{
  public:
       MyMutex()  { CON_MUTEX(mutex);}
      ~MyMutex()  { DEL_MUTEX(mutex);}

  private:
      MUTEX    mutex;

      friend class MyLock;
      void lock()        {LOCK_MUTEX(mutex);}
      void unlock()      {UNLOCK_MUTEX(mutex);}

      MyMutex(const MyMutex& copy); // No Definition to stop copying.
      MyMutex& operator=(const MyMutex& copy);
};

class MyLock
{
   public: MyLock(MyMutex& m): mutex(m)  {mutex.lock();}
          ~MyLock()                      {mutex.unlock();}
   private:
       MyMutex& mutex;
};



So how would you use this?
Will first you build a Mutex. Then you create a lock for it.
CODE
void func1()
{
  static MyMutex      mutex;
  MyLock       lock(mutex);

  // Do Somthing important.
}



From the above usage model I hope you can see that a programmer can never lock a mutex and then accidentally leave it locked. The lock is acquired automatically on construction of the lock. The thread is then guaranteed it is the only thread doing something in the function body and when it is finished the lock goes out of scope and automatically unlocks the mutex.


Example: Say you wanted only one thread inside an object at any given time.

CODE
class MySingleThreadedClass
{
   public:  void func1();
            void func2();
   private:
       MyMutex   m_mutex;
};

void MySingleThreadedClass::func1()
{
   MyLock    lock(m_mutex);
   // STUFF
}

void MySingleThreadedClass::func2()
{
   MyLock    lock(m_mutex);
   // Other Stuff
}


Here we are sharing m_mutex between func1() and func2().
So we can guarantee that only one thread is active inside the object. i.e. If a thread is messing around inside func1(). Another thread attempting to call func2() will be suspended at the lock until the first thread in func1() is finished and releases its lock.

Anybody want to try and implement trylock().

Remember it has to be guarantee a symmetric call to unlock() but only if the lock() call succeeded. Also we need to be able to test to see if we did manage to acquire the lock.


Next we need to look at properties.
How would we implement properties without incurring the runtime cost associated with polymorphism and virtual functions.

raduking - November 8, 2005 05:18 PM (GMT)
I think you need the copy constructor defined otherwise the linker won't be able to link the following line:

public: MyLock(MyMutex& m): mutex(m) {mutex.lock();}

myork - November 8, 2005 06:28 PM (GMT)
QUOTE (raduking @ Nov 8 2005, 12:18 PM)
I think you need the copy constructor defined otherwise the linker won't be able to link the following line:

public: MyLock(MyMutex& m): mutex(m) {mutex.lock();}


You should try compiling before making silly comments.
Then go back and look at the code and try and figure why the copy constructor is not needed.




Hosted for free by InvisionFree