RWLock — это примитив синхронизации, который позволяет одновременное чтение и монопольную запись.
Те.
чтение блокирует запись, но не блокирует чтение других потоков, а запись блокирует всё.
Итак, этот очень полезный примитив доступен в потоках posix и в Windows, начиная с Vista. Для Windows XP/2003 вам придется сделать это из двух критических разделов и события (у UPD на самом деле есть лучший вариант, см.
UPD в конце статьи).
Давайте покажем, как это выглядит (код предоставлен StackOverflow и слегка переведен с C на C++):
А вот как могла бы выглядеть эта же красота, если использовать только системы Vista+: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; };
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++
-
Улучшенный Видеоплеер Ffmpeg.
19 Oct, 24 -
Apacetosh
19 Oct, 24 -
Впечатления От Викимании 2010
19 Oct, 24