Encountered a particularly interesting little problem today. The requirement was to create a read/write locking mechanism for .NET that behaved according to these rules:
- Any number of read operations can happen simultaneously.
- Only one write can happen at a time.
- Read and write operations cannot happen simultaneously. (i.e., a write cannot begin until any outstanding read operations complete.)
- Write operations should not starve forever during read-heavy periods.
That's a description of a fairly standard read/write lock. What makes it more interesting is that this read/write lock must protect a resource across multiple processes. Thus, most of the standard .NET threading constructs are useless, since they're only good for coordinating multiple threads inside a single process.
Win32 does provide a few cross-process synchronization primitives, however: Mutexes, Semaphores, and Events. These can be used from .NET with relative ease (i.e., relative to many other interop chores). Between my friend John and me, we figured out that you can actually satisfy the requirements using only Mutex and Semaphore. It sure isn't pretty, though.
Start by picking a number that is slightly higher than the number of concurrent reads you can reasonably expect. In this case, let's say it's 100. We will create a global semaphore with this number of permits. We'll also create a global mutex. (The code that follows should be considered pseudocode: for illustration purposes only.)
// create or open global mutex GlobalMutex mutex = new GlobalMutex("IdOfProtectedResource.Mutex"); // create or open global semaphore GlobalSemaphore semaphore = new GlobalSemaphore("IdOfProtectedResource.Semaphore", 100); public void AcquireReadLock() { mutex.Acquire(); semaphore.Acquire(); mutex.Release(); } public void ReleaseReadLock() { semaphore.Release(); } public void AcquireWriteLock() { mutex.Acquire(); for (int i = 0; i < 100; i++) semaphore.Acquire(); mutex.Release(); } public void ReleaseWriteLock() { for (int i = 0; i < 100; i++) semaphore.Release(); }
Thus, having read lock simply means holding one of the semaphore's permits, while having write lock means holding all of the semaphore's permits. It's a little bit nasty and inefficient, but it seems like it should work.
Tune in next week, when we'll be creating a FIFO monitor using only bubble gum and a pocket watch. ;)