My Mutex Implementation

There are already lots of well-known and popular libraries out there that provide mutices. But I wanted to try implementing it myself for the sake of just making it. Hey, it’s summer session at DigiPen and I don’t have to work on any school game projects. This means I can do whatever programming exercise I want during this time 🙂

My implementation relies on “Windows.h”, which already provides mutex objects. But hey, it’s summer session! Besides, I actually find my interface design more intuitive and easier to use, so I might end up using it in my next game project.

Below is the interface for the Mutex class. It has a default constructor, a copy constructor and a destructor. It also has an assignment operator. Finally, it defines the very two operations a mutex object has: lock and unlock.

The lockPtr_ member represents shared lock among mutices guarding the same resource. The hasLock_ member indicates whether this mutex has been acquired or not. The referenceCountPtr_ is for counting references of mutices for the same resource.

class Mutex
{
  private:
    typedef unsigned long ReferenceCount;
    typedef LONG Lock;
    
    Lock *lockPtr_;
    bool hasLock_;
    ReferenceCount *referenceCountPtr_;

  public:
    Mutex(void);
    Mutex(const Mutex &clone);
    ~Mutex(void);
    
    Mutex &operator=(const Mutex &rhs);

    void lock(void);
    void unlock(void);
  }
};

Default Constructor

The default constructor does pretty much you’d expect a mutex constructor to do. The lock is initialized to one, which denotes the state of being open to acquisition, and the reference counter is, of course, initialized to one.

Mutex::Mutex(void)
  : lockPtr_(new Lock(1UL))
  , hasLock_(false)
  , referenceCountPtr_(new ReferenceCount(1UL))
{ }

Copy Constructor

The copy constructor copies over all the underlying pointers, and it also increments the reference counter by one.

Mutex::Mutex(const Mutex &clone)
  : lockPtr_(clone.lockPtr_)
  , hasLock_(false)
  , referenceCountPtr_(clone.referenceCountPtr_)
{
  InterlockedIncrement(referenceCountPtr_);
}

Destructor

The destructor decrements the reference counter; also, if the mutex finds itself the last one associated with the lock, it frees all the underlying pointers.

Mutex::~Mutex(void)
{
  //check if this is the last reference
  if (InterlockedDecrement(referenceCountPtr_) == 0UL)
  {
    //free resource
    delete lockPtr_;
    delete referenceCountPtr_;
  }
}

Assignment Operator

The assignment operator has to check whether this mutex is the last one associated with the lock. If yes, it frees all the underlying pointers. Finally, it associates itself with another mutex’s lock.

Mutex &Mutex::operator=(const Mutex &rhs)
{
  //check self-assignment
  if (&rhs == this) return *this;

  //check target resource
  if (rhs.lockPtr_ == lockPtr_) return *this;

  //force release
  if (hasLock_) unlock();

  //check if this is the last reference
  if (InterlockedDecrement(referenceCountPtr_) == 0UL)
  {
    //free resource
    delete lockPtr_;
    delete referenceCountPtr_;
  }

  //copy mutex
  lockPtr_ = rhs.lockPtr_;
  hasLock_ = false;
  referenceCountPtr_ = rhs.referenceCountPtr_;
  InterlockedIncrement(referenceCountPtr_);

  return *this;
}

Lock

The lock() method is a little more complicated, so I’ll explain the details in the comments. It is making use of the atomic operation InterlockedExchange provided by Windows API.

void Mutex::lock(void)
{
  //do nothing if lock is already acquired
  if (hasLock_) return;

  //try acquire lock
  if (InterlockedExchange(lockPtr_, 0UL))
  {
    //lock acquired, return
    hasLock_ = true;
    return;
  }
  //lock not acquired
  else
  {
    //wait until lock is acquired
    while (1)
    {
      //try acquire lock
      if (InterlockedExchange(lockPtr_, 0UL))
      {
        //lock acquired, return
        hasLock_ = true;
        return;
      }
      else
      {
      //lock not acquired, yield time slice
        Sleep(1);
      }
    }
  }
}

Unlock

The unlock() method is much more simpler than lock(). It just “puts” the acquired “one” back to the lock.

void Mutex::unlock(void)
{
  if (hasLock_)
  {
    hasLock_ = false;
    InterlockedExchange(lockPtr_, 1UL);
  }
}

Finished!

That’s it. I do love the simplicity of the interface. By the design of its copy constructor and assignment operator, the Mutex class is to be passed by value among threads.

//...some code...

const int NUM_ITERATIONS = 10000;

int main(void)
{
  //...some code...
  
  Mutex mutex;
  int i = 0;
  
  Thread(new Counter(&i, mutex, NUM_ITERATIONS)).start();
  Thread(new Counter(&i, mutex, NUM_ITERATIONS)).start();
  
  //...some code...
  
  //after the two threads have finished, 
  //i should be exactly (2 * NUM_ITERATIONS)
}

Counter::Counter(int *target, Mutex mutex, int numIterations)
  : target_(target)
  , mutex_(mutex)
  , numIterations_(numIterations)
{ }

Counter::run(void)
{
  for (int i = 0; i < numIterations_; ++i)
  {
    mutex_.lock();
      
      ++(*target_);
      
    mutex.unlock();
  }
}

About Allen Chou

Physics / Graphics / Procedural Animation / Visuals
This entry was posted in C/C++. Bookmark the permalink.

One Response to My Mutex Implementation

  1. boy says:

    mutex!
    well, so miss C language~~~
    process and thread~~~

Leave a Reply