HOWTO Make Windows SlimRW Lock More Confortable

Recently I tripped into SlimRW Lock API.

It’s a Windows synchronization object very similar to the critical section, with the support of two different types of lock: shared and exclusive.

Essentially you should get the shared lock for all read-only activities, while the exclusive lock shall be used for all the other cases.

The Basic Implementation

I wrote this simple class to include them in my program:

class ClassSRWLock {
private:
    ::SRWLOCK srw;
public:
    ClassSRWLock ();
    void Lock (bool fExclusive);
    void Unlock (bool fExclusive);
};

The implementation was very simple:

ClassSRWLock::ClassSRWLock ()
{
    ::InitializeSRWLock (&this->srw);
}

// lock object
void ClassSRWLock::Lock (bool fExclusive)
{
    if (fExclusive)
        ::AcquireSRWLockExclusive (&this->srw);
    else
        ::AcquireSRWLockShared (&this->srw);
}

// unlock object
void ClassSRWLock::Unlock (bool fExclusive)
{
     if (fExclusive)
         ::ReleaseSRWLockExclusive (&this->srw);
     else
         ::ReleaseSRWLockShared (&this->srw);
}

This small effort should improve the internal synchronization processing of my programs.

The Problem

During my first tests, I discovered that the following lines of code hang the current thread:

ClassSRWLock srw;

this->Lock (true);
this->Lock (false); // <== the thread hangs here
this->Unlock (false);
this->Unlock (true);

It seems that we cannot ask for any other locks while we have the exclusive one.
This means, for example, that we cannot call a shared lock function from an exclusive lock one.

Naturally, this increases the efforts to use these objects, making the process much more difficult than desired.

The Solution

The basic idea of my solution, it that we need to add these two new features:

  • the object should be able to track down the current exclusive thread (if any)
  • if we are this exclusive thread, all our nested lock & unlock requests shall be skipped.

Another important side aspect to care about is, that these tasks should be implemented with low computation and synchronization costs.
This last consideration will try to keep intact the advantages of using these kinds of objects.

The Implementation

So I slightly updated my Class in the following way:

class ClassSRWLock {
private:
    ::SRWLOCK srw;
    // to support nested locks
    unsigned idThread_W;
    int nested_locks;
public:
    ClassSRWLock ();
    void Lock (bool fExclusive);
    void Unlock (bool fExclusive);
};

The constructor shall only initialize the new class members

ClassSRWLock::ClassSRWLock ()
{
    ::InitializeSRWLock (&this->srw);
    // init class data
    this->idThread_W = -1;
    this->nested_locks = 0;
}

Now the Lock() method shall do most of the magic.

It shall track who has the exclusive lock and eventually suppress any nested lock request.

void ClassSRWLock::Lock (bool fExclusive)
{
    unsigned idThread;

    idThread = ::GetCurrentThreadId ();
    // let's see if it's a valid nested lock
    if (idThread == this->idThread_W)
    {
        this->nested_locks++;
        return;
    }
    // execute lock.
    if (fExclusive)
    {
        ::AcquireSRWLockExclusive (&this->srw);
        // track exclusive lock
        this->idThread_W = idThread;
        this->nested_locks = 0;
    }
    else
        ::AcquireSRWLockShared (&this->srw);
}

The Unlock() method is working in a similar way to skip any nested unlock request.

void ClassSRWLock::Unlock (bool fExclusive)
{
    unsigned idThread;

    idThread = ::GetCurrentThreadId ();
    // let's see if it's a valid nested lock
    if (idThread == this->idThread_W)
    {
        // should i skip it?
        if (this->nested_locks > 0)
        {
            this->nested_locks--;
            return;
        }
        // else we are going to release it
        this->idThread_W = -1;
    }
    if (fExclusive)
        ::ReleaseSRWLockExclusive (&this->srw);
    else
        ::ReleaseSRWLockShared (&this->srw);
}

Because of the limited processing of this->idThread_W, we don’t even need to synchronize its access.

It is indirectly taking advantage of the SlimRW Lock synchronization mechanics, because every time it is updated, the current thread has gained exclusive access.

 

In conclusion, the resulting class implementation offers a sensible functional improvement on Windows SlimRW Locks with almost no overhead or performance cost.

 

Further Sources About