[Из песочницы] Портабельный RWLock для Windows
RWLock — это такой примитив синхронизации, позволяющий одновременное чтение и эксклюзивную запись. Т.е. чтение блокирует запись, но не блокирует чтение других тредов, а запись блокирует все.
Так вот, этот весьма полезный примитив имеется в posix threads и в Windows от Vista и далее. Для Windows XP/2003 приходится изготавливать сей, весьма полезный, примитив из двух критических секций и события.
А вот как эта же красота могла бы выглядеть при использовании только Vista+ систем:
Мало того, что выглядит до безобразия просто, еще и работает на порядок быстрее. Но, есть одно но, как всегда… При попытке запустить приложение содержащее этот код (конечно мы умные ребята, сделали определение версии и для XP хотим использовать первый вариант, а для новых систем — второй), получим сообщение типа: «ой, а вот функции InitializeSRWLock что-то не нашлось в kernel32» после чего наше приложение любезно будет прибито.
Выглядит кучерявей, зато стало портабельно. Осталось сделать враппер автоматически выбирающий нужный вариант в зависимости от версии Windows:
И в завершении автолокер:
Реализация с использованием pthread ничем не отличается от первой версии SRWLock за исключением других имен вызываемых функций.
Так вот, этот весьма полезный примитив имеется в 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 за исключением других имен вызываемых функций.