Portable RWLock for Windows

RWLock is such a synchronization primitive that allows simultaneous reading and exclusive writing. Those. reading blocks writing, but does not block reading other threads, and writing blocks everything.

So, this very useful primitive is available on posix threads and on Windows from Vista onwards. For Windows XP / 2003, you have to produce it from two critical sections and events (UPD is actually a better option, see UPD at the end of the article).

Let's show how it looks (the code was kindly provided by StackOverflow and slightly translated from C to C ++):

class RWLockXP // Implementation for Windows XP
{
public:
    RWLockXP()
        : countsLock(),
          writerLock(),
          noReaders(),
          readerCount(0),
          waitingWriter(FALSE)
    {
        InitializeCriticalSection(&writerLock);
        InitializeCriticalSection(&countsLock);
       /*
        * Could use a semaphore as well.  There can only be one waiter ever,
        * so I'm showing an auto-reset event here.
        */
        noReaders = CreateEvent (NULL, FALSE, FALSE, NULL);
    }
    ~RWLockXP()
    {
        DeleteCriticalSection(&writerLock);
        DeleteCriticalSection(&countsLock);
        CloseHandle(noReaders);
    }
    void readLock()
    {
        /**
         * We need to lock the writerLock too, otherwise a writer could
         * do the whole of rwlock_wrlock after the readerCount changed
         * from 0 to 1, but before the event was reset.
         */
        EnterCriticalSection(&writerLock);
        EnterCriticalSection(&countsLock);
        ++readerCount;
        LeaveCriticalSection(&countsLock);
        LeaveCriticalSection(&writerLock);
    }
    void readUnLock()
    {
        EnterCriticalSection(&countsLock);
        assert (readerCount > 0);
        if (--readerCount == 0)
        {
            if (waitingWriter)
            {
                /*
                 * Clear waitingWriter here to avoid taking countsLock
                 * again in wrlock.
                 */
                waitingWriter = FALSE;
                SetEvent(noReaders);
            }
        }
        LeaveCriticalSection(&countsLock);
    }
    void writeLock()
    {
        EnterCriticalSection(&writerLock);
        /*
         * readerCount cannot become non-zero within the writerLock CS,
         * but it can become zero...
         */
        if (readerCount > 0)
        {
            EnterCriticalSection(&countsLock);
            /* ... so test it again.  */
            if (readerCount > 0)
            {
                waitingWriter = TRUE;
                LeaveCriticalSection(&countsLock);
                WaitForSingleObject(noReaders, INFINITE);
            }
            else
            {
                /* How lucky, no need to wait.  */
                LeaveCriticalSection(&countsLock);
            }
        }
        /* writerLock remains locked.  */
    }
    void writeUnLock()
    {
        LeaveCriticalSection(&writerLock);
    }
private:
    CRITICAL_SECTION countsLock;
    CRITICAL_SECTION writerLock;
    HANDLE noReaders;
    int readerCount;
    BOOL waitingWriter;
};

And here is how the same beauty would look if using only Vista + systems:

class RWLockSRW // For Windows Vista+ based on Slim RWLock
{
public:
    RWLockSRW()
        : srwLock()
    {
        InitializeSRWLock(&srwLock);
    }
    ~RWLockSRW()
    {
    }
    void readLock()
    {
        AcquireSRWLockShared(&srwLock);
    }
    void readUnLock()
    {
        ReleaseSRWLockShared(&srwLock);
    }
    void writeLock()
    {
        AcquireSRWLockExclusive(&srwLock);
    }
    void writeUnLock()
    {
        ReleaseSRWLockExclusive(&srwLock);
    }
private:
    RTL_SRWLOCK srwLock;
};

Not only does it look ugly simply, it also works an order of magnitude faster. But, there is one thing but, as always ... When trying to run an application containing this code (of course we are smart guys, we made the version determination and want to use the first option for XP, and the second one for new systems), we get a message like: “oh, but InitializeSRWLock functions, something was not found in kernel32 "after which our application will be kindly nailed.

The solution is to load Slim RWLock functions dynamically using LoadLibrary, function pointers, and that’s all:

typedef void(__stdcall *SRWLock_fptr)(PSRWLOCK);
class RWLockSRW // For Windows Vista+ based on Slim RWLock
{
public:
    RWLockSRW()
        : hGetProcIDDLL(NULL), 
        AcquireSRWLockShared_func(NULL),
        ReleaseSRWLockShared_func(NULL),
        AcquireSRWLockExclusive_func(NULL),
        ReleaseSRWLockExclusive_func(NULL),
        srwLock()
    {
        wchar_t path[MAX_PATH] = { 0 };
        GetSystemDirectory(path, sizeof(path));
        std::wstring dllPath = std::wstring(path) + L"\\kernel32.dll";
        HINSTANCE hGetProcIDDLL = LoadLibrary(dllPath.c_str());
        if (!hGetProcIDDLL)
        {
            throw std::exception("SRWLock Error loading kernel32.dll");
        }
        AcquireSRWLockShared_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "AcquireSRWLockShared");
        if (!AcquireSRWLockShared_func)
        {
            throw std::exception("SRWLock Error loading AcquireSRWLockShared");
        }
        ReleaseSRWLockShared_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "ReleaseSRWLockShared");
        if (!ReleaseSRWLockShared_func)
        {
            throw std::exception("SRWLock Error loading ReleaseSRWLockShared");
        }
        AcquireSRWLockExclusive_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "AcquireSRWLockExclusive");
        if (!AcquireSRWLockExclusive_func)
        {
            throw std::exception("SRWLock Error loading AcquireSRWLockExclusive");
        }
        ReleaseSRWLockExclusive_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "ReleaseSRWLockExclusive");
        if (!ReleaseSRWLockExclusive_func)
        {
            throw std::exception("SRWLock Error loading ReleaseSRWLockExclusive");
        }
        SRWLock_fptr InitializeSRWLock_func = (SRWLock_fptr)GetProcAddress(hGetProcIDDLL, "InitializeSRWLock");
        if (!InitializeSRWLock_func)
        {
            throw std::exception("SRWLock Error loading InitializeSRWLock");
        }
        InitializeSRWLock_func(&srwLock);
    }
    ~RWLockSRW()
    {
        if (hGetProcIDDLL)
        {
            FreeLibrary(hGetProcIDDLL);
        }
    }
    void readLock()
    {
        if (AcquireSRWLockShared_func)
        {
            AcquireSRWLockShared_func(&srwLock);
        }
    }
    void readUnLock()
    {
        if (ReleaseSRWLockShared_func)
        {
            ReleaseSRWLockShared_func(&srwLock);
        }
    }
    void writeLock()
    {
        if (AcquireSRWLockExclusive_func)
        {
            AcquireSRWLockExclusive_func(&srwLock);
        }
    }
    void writeUnLock()
    {
        if (ReleaseSRWLockExclusive_func)
        {
            ReleaseSRWLockExclusive_func(&srwLock);
        }
    }
private:
    HINSTANCE hGetProcIDDLL;
    SRWLock_fptr AcquireSRWLockShared_func;
    SRWLock_fptr ReleaseSRWLockShared_func;
    SRWLock_fptr AcquireSRWLockExclusive_func;
    SRWLock_fptr ReleaseSRWLockExclusive_func;
    RTL_SRWLOCK srwLock;
};

It looks curly-headed, but it has become portable. It remains to make the wrapper automatically select the desired option depending on the version of Windows:

class RWLock // Wrapper
{
public:
     RWLock()
         : rwLockXP(NULL), rwLockSRW(NULL), isVistaPlus(IsWindowsVistaOrGreater())
     {
          if (isVistaPlus)
          {
               rwLockSRW = new RWLockSRW();
          }
          else
          {
               rwLockXP = new RWLockXP();
          }
     }
     ~RWLock()
     {
          if (isVistaPlus)
          {
               delete rwLockSRW;
          }
          else
          {
               delete rwLockXP;
          }
     }
     void readLock()
     {
          if (isVistaPlus)
          {
               rwLockSRW->readLock();
          }
          else
          {
               rwLockXP->readLock();
          }
     }
     void readUnLock()
     {
          if (isVistaPlus)
          {
               rwLockSRW->readUnLock();
          }
          else
          {
               rwLockXP->readUnLock();
          }
     }
     void writeLock()
     {
          if (isVistaPlus)
          {
               rwLockSRW->writeLock();
          }
          else
          {
               rwLockXP->writeLock();
          }
     }
     void writeUnLock()
     {
          if (isVistaPlus)
          {
               rwLockSRW->writeUnLock();
          }
          else
          {
               rwLockXP->writeUnLock();
          }
     }
private:
     RWLockXP  *rwLockXP;
     RWLockSRW *rwLockSRW;
     bool isVistaPlus;
};

And at the end of the autolocker:

class ScopedRWLock
{
public:
    ScopedRWLock(RWLock *lc_, bool write_ = false)
        : lc(*lc_), write(write_)
    {
        if (write)
        {
            lc.writeLock();
        }
        else
        {
            lc.readLock();
        }
    }
    ~ScopedRWLock()
    {
        if (write)
        {
            lc.writeUnLock();
        }
        else
        {
            lc.readUnLock();
        }
    }
private:
    RWLock &lc;
    bool write;
    // Non copyable!
    static void *operator new(size_t);
    static void operator delete(void *);
    ScopedRWLock(const ScopedRWLock&);
    void operator=(const ScopedRWLock&);
};

The implementation using pthread is no different from the first version of SRWLock, with the exception of other names of the called functions.

UPD:
Here they suggested that there are wonderful undocumented functions (they are present already with Windows 2000 and still exist (as of January 31, 2017 in Windows 10 they haven’t gone anywhere)), they work better than crutches from the event and mutexes.
I give an option with their use (I recommend using it):

typedef struct _RTL_RWLOCK
{
    RTL_CRITICAL_SECTION rtlCS;
    HANDLE hSharedReleaseSemaphore;
    UINT   uSharedWaiters;
    HANDLE hExclusiveReleaseSemaphore;
    UINT   uExclusiveWaiters;
    INT    iNumberActive;
    HANDLE hOwningThreadId;
    DWORD  dwTimeoutBoost;
    PVOID  pDebugInfo;
} RTL_RWLOCK, *LPRTL_RWLOCK;
typedef void(__stdcall *RtlManagePtr)(LPRTL_RWLOCK);
typedef BYTE(__stdcall *RtlOperatePtr)(LPRTL_RWLOCK, BYTE);
class RWLockXP // Implementation for Windows XP
{
public:
    RWLockXP()
        : hGetProcIDDLL(NULL),
        RtlDeleteResource_func(NULL),
        RtlReleaseResource_func(NULL),
        RtlAcquireResourceExclusive_func(NULL),
        RtlAcquireResourceShared_func(NULL),
        rtlRWLock()
    {
        wchar_t path[MAX_PATH] = { 0 };
        GetSystemDirectory(path, sizeof(path));
        std::wstring dllPath = std::wstring(path) + L"\\ntdll.dll";
        HINSTANCE hGetProcIDDLL = LoadLibrary(dllPath.c_str());
        if (hGetProcIDDLL)
        {
            RtlDeleteResource_func = (RtlManagePtr)GetProcAddress(hGetProcIDDLL, "RtlDeleteResource");
            if (!RtlDeleteResource_func)
            {
                return;
            }
            RtlReleaseResource_func = (RtlManagePtr)GetProcAddress(hGetProcIDDLL, "RtlReleaseResource");
            if (!RtlReleaseResource_func)
            {
                return;
            }
            RtlAcquireResourceExclusive_func = (RtlOperatePtr)GetProcAddress(hGetProcIDDLL, "RtlAcquireResourceExclusive");
            if (!RtlAcquireResourceExclusive_func)
            {
                return;
            }
            RtlAcquireResourceShared_func = (RtlOperatePtr)GetProcAddress(hGetProcIDDLL, "RtlAcquireResourceShared");
            if (!RtlAcquireResourceShared_func)
            {
                return;
            }
            RtlManagePtr RtlInitializeResource_func = (RtlManagePtr)GetProcAddress(hGetProcIDDLL, "RtlInitializeResource");
            if (RtlInitializeResource_func)
            {
                RtlInitializeResource_func(&rtlRWLock);
            }
        }
    }
    ~RWLockXP()
    {
        if (RtlDeleteResource_func)
        {
            RtlDeleteResource_func(&rtlRWLock);
        }
        if (hGetProcIDDLL)
        {
            FreeLibrary(hGetProcIDDLL);
        }
    }
    void readLock()
    {
        if (RtlAcquireResourceShared_func)
        {
            RtlAcquireResourceShared_func(&rtlRWLock, TRUE);
        }
    }
    void readUnLock()
    {
        if (RtlReleaseResource_func)
        {
            RtlReleaseResource_func(&rtlRWLock);
        }
    }
    void writeLock()
    {
        if (RtlAcquireResourceExclusive_func)
        {
            RtlAcquireResourceExclusive_func(&rtlRWLock, TRUE);
        }
    }
    void writeUnLock()
    {
        if (RtlReleaseResource_func)
        {
            RtlReleaseResource_func(&rtlRWLock);
        }
    }
private:
    HINSTANCE hGetProcIDDLL;
    RtlManagePtr RtlDeleteResource_func;
    RtlManagePtr RtlReleaseResource_func;
    RtlOperatePtr RtlAcquireResourceExclusive_func;
    RtlOperatePtr RtlAcquireResourceShared_func;
    RTL_RWLOCK rtlRWLock;
};

Also popular now: