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

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