📜 ⬆️ ⬇️

Portable RWLock for Windows

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

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

Let's show how it looks (code courtesy of 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; }; 

But how could this beauty look like when 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 outrageously simple, it also works an order of magnitude faster. But, there is one thing, but, as always ... When trying to launch an application containing this code (of course, we are smart guys, we made the definition of the version and for XP we want to use the first option, and for the new systems - the second), we get a message like: “oh, but InitializeSRWLock function was not found in kernel32, after which our application would be kindly nailed.

The solution is to load the Slim RWLock functions dynamically using LoadLibrary, function pointers and all this:

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

Looks curly, but it became 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 autolocher:

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

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

UPD:
They suggested that there are wonderful undocumented functions (they are present even with Windows 2000 and they still exist (as of January 31, 2017 in Windows 10 they have not gone away)), 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; }; 

Source: https://habr.com/ru/post/317958/


All Articles