RWLock — это такой примитив синхронизации, позволяющий одновременное чтение и эксклюзивную запись. Т.е. чтение блокирует запись, но не блокирует чтение других тредов, а запись блокирует все.
Так вот, этот весьма полезный примитив имеется в posix threads и в Windows от Vista и далее. Для Windows XP/2003 приходится изготавливать сей, весьма полезный, примитив из двух критических секций и события.
Покажем как это выглядит (код любезно предоставлен 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() { writerLock.destroy(); readerCountLock.destroy(); noReaders.destroy(); } 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 за исключением других имен вызываемых функций.
ссылка на оригинал статьи https://habrahabr.ru/post/317958/
Добавить комментарий