In the previous post we had discussed about condition variable and we demonstrated it using the commonly used Critical Section Synchronization objects. Now let’s check what Slim Reader Writer Lock is.
It’s again a user mode synchronization object which allows concurrent access to a shared resource for multiple threads and exclusive write access to the writer thread. Earlier if you had to implement suck kind of mechanism, we’ll have to write your own code or to get expert written code from some text books or to steal somewhere. This mechanism is broadly used where data is reads frequently with multiple thread and infrequent update of the shared resource.
It designed to be fast, small and for resource efficiency hence it wont support recursive acquisition of resources as it may create additional overhead.
Here are the functions used to implement Slim Reader and writer locks.
There are two modes to access the resource
- Shared mode
grants shared read-only access to multiple reader threads, which enables them to access data from the shared resource concurrently. If read operations exceed write operations, this concurrency increases performance and throughput compared to critical sections.
- Exclusive mode grants read/write access to one writer thread at a time. When the lock has been acquired in exclusive mode, no other thread can access the shared resource until the writer releases the lock.
The locks should be initilized by using InitializeSRWLock function with SRWLOCK structure.
VOID WINAPI InitializeSRWLock(PSRWLOCK SRWLock);
AcquireSRWLockExclusive and ReleaseSRWLockExclusive apis can be used to acquire and release exclusive access to resource respectively. On the other hand, AcquireSRWLockShared and ReleaseSRWLockShared functions can be used to acquire and release shared access to the resource.
We’ve discussed about condition variables before. SRW locks can be used with condition variables apis to implement user mode wait operations for the threads running in a single process. SleepConditionVariableSRW api can be used to wait for a condition variables with SRW lock. SRW lock will be released when it’s being used with SleepConditionVariableSRW lock function.
[sourcecode language='cpp']
BOOL WINAPI SleepConditionVariableSRW(
PCONDITION_VARIABLE ConditionVariable,
PSRWLOCK SRWLock,
DWORD dwMilliseconds,
ULONG Flags
);
[/sourcecode]
The parameter is Flag to this API is important because there we can specify the mode of locking the resource as shared or exclusive access upon the successful execution of the API. If the flag parameter is CONDITION_VARIABLE_LOCKMODE_SHARED on returning the function the SRW lock will be granted with shared access and otherwise an exclusive access.
Let’s take a simple example which is available with Jeffry Richter’s “Windows Via C++” published in his website. I’ve made it short for the sake of expressing it here.
[sourcecode language='cpp']
BOOL ConsumeElement(int nThreadNum, int nRequestNum, HWND hWndLB)
{
// Get access to the queue to consume a new element
AcquireSRWLockShared(&g_srwLock);
// Fall asleep until there is something to read.
while (…) {
// There was not a readable element
SleepConditionVariableSRW(&g_cvReadyToConsume, &g_srwLock,
INFINITE, CONDITION_VARIABLE_LOCKMODE_SHARED);
}
// Get the first new element
CQueue::ELEMENT e;
// Note: No need to test the return value since IsEmpty
// returned FALSE
g_q.GetNewElement(nThreadNum, e);
// No need to keep the lock any longer
ReleaseSRWLockShared(&g_srwLock);
// A free slot is now available for writer threads to produce
// –> wake up a writer thread
WakeConditionVariable(&g_cvReadyToProduce);
return(TRUE);
}
DWORD WINAPI ReaderThread(PVOID pvParam)
{
for (…)
{
if (!ConsumeElement(nThreadNum, nRequestNum, hWndLB))
return(0);
Sleep(2500); // Wait before reading another element
}
return(0);
}
DWORD WINAPI WriterThread(PVOID pvParam) {
int nThreadNum = PtrToUlong(pvParam);
for (…)
{
CQueue::ELEMENT e = { nThreadNum, nRequestNum };
// Require access for writing
AcquireSRWLockExclusive(&g_srwLock);
// If the queue is full, fall asleep as long as the condition variable
// is not signaled
if (…) {
// No more room in the queue Need to wait for a reader to empty a slot before acquiring the lock again
SleepConditionVariableSRW(&g_cvReadyToProduce, &g_srwLock,
INFINITE, 0);
}
// Add the new ELEMENT into the queue
g_q.AddElement(e);
// No need to keep the lock any longer
ReleaseSRWLockExclusive(&g_srwLock);
// Signal reader threads that there is an element to consume
WakeAllConditionVariable(&g_cvReadyToConsume);
// Wait before adding a new element
Sleep(1500);
}
return(0);
}
[/sourcecode]
I removed lot of lines from the original code written by Jeff. You can download the entire source code from his website. One comment about his code is, it’s remarkably professional.
And finally a SRW lock doesn’t have APIs to destroy the SRW lock handle.
You might be interested to see the some performance figure presented in MSDN magazine on SRW locks.
