Портативный Rwlock Для Windows

RWLock — это примитив синхронизации, который позволяет одновременное чтение и монопольную запись.

Те.

чтение блокирует запись, но не блокирует чтение других потоков, а запись блокирует всё.

Итак, этот очень полезный примитив доступен в потоках posix и в Windows, начиная с Vista. Для Windows XP/2003 вам придется сделать это из двух критических разделов и события (у UPD на самом деле есть лучший вариант, см.

UPD в конце статьи).

Давайте покажем, как это выглядит (код предоставлен StackOverflow и слегка переведен с C на 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; };

А вот как могла бы выглядеть эта же красота, если использовать только системы Vista+:

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

Мало того, что это выглядит возмутительно просто, так еще и работает на порядок быстрее.

Но, есть, как всегда, одно но.

Когда мы пытаемся запустить приложение, содержащее этот код (мы, конечно, умные ребята, определили версию и для XP хотим использовать первый вариант, а для новых систем — второй), мы получим сообщение типа: «ой, но функция InitializeSRWLock что-то не найдена в kernel32», после чего наше приложение будет любезно убито.

Решение — динамически загружать функции Slim RWLock с помощью LoadLibrary, указателей на функции и все:

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

Он выглядит фигурным, но стал портативным.

Остается только сделать обертку, которая автоматически выбирает нужный вариант в зависимости от версии 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; };

И, наконец, автоблокировка:

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

Реализация с использованием pthread ничем не отличается от первой версии SRWLock, за исключением других названий вызываемых функций.

УПД: Здесь Подсказали, что есть отличные недокументированные функции (они присутствовали с Windows 2000 и существуют до сих пор (по состоянию на 31.01.2017 не исчезли в Windows 10)), работают лучше, чем костыли событий и мьютексы.

Вот вариант их использования (рекомендую использовать этот):

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

Теги: #RWLock #многопоточность #Slim RWLock в Windows XP #C++ #C++

Вместе с данным постом часто просматривают: