Bug in code made me aware of new synchronization primitive ReaderWriterLockSlim. This sync primitive is suited to protect a resource for scenario when multiple threads are involved with read access and one thread is involved with exclusive write access.
Let us understand why ReadWriterLock may not be suited to protect a resource in this scenario. Let code using ReadWriterLock protecting resource plan to release the reader lock and upgrade to the write lock. In between releasing reader lock and acquiring writer lock, there is possibility for change in state state that was read during the upgrade decision. If we upgraded and released the read lock, two threads attempting to simultaneously upgrade would deadlock one another.
ReaderWriterLockSlim has 3 lock modes Read, Write, UpgradeableRead and has methods EnterReadLock, EnterWriteLock, EnterUpgradeableReadLock, and corresponding TryEnterXXLock and ExitXXLock methods. We understand
- Read is a shared lock, where any number of threads can acquire the lock in the Read mode simultaneously
- Write is a mutual exclusion lock, where no other threads are permitted to simultaneously hold the lock in any mode.
- UpgradeableRead allows safe upgrade from Read to Write mode. While there can be any number of threads in Read mode, there is one UpgradeableRead owner
Using EnterUpgradeableReadLock instead of EnterReadLock, program is checking whether condition/scenario is in order for entering the write lock. No changes occurs between checking the condition and entering the write lock. This is advantage you get.
ReaderWriterLockSlim supports recursion and find it interesting the way it treats recursion. Recursion can be turned on at construction time passing enum value. The recursion policy for a given lock can be get using RecursionPolicy property. Even with recursion policy one cannot acquire a Write lock when a Read lock is held. Here are some pointers I noted.
- EnterWriteLock block other threads that wants to read or write. On holding an up-gradable lock, other threads can still get read locks
- You cannot call EnterReadLock twice on the same thread without first exiting the lock. This is similar with the other modes.
- Only one thread can enter up-gradable lock. when thread tries to enter upgradable lock, thread is blocked if there are threads waiting to enter write mode, or if there is a single thread in write mode.
- Write lock cannot be taken before read is released. The ReaderWriterLockSlim class essentially wrappers the write lock and allows all readers to read so long as the write lock isnt held. Once the write lock is held no readers can read.
- When acquiring a write lock, if you already have a read lock, you’ll need to upgrade lock. After you upgrade & complete the task, you need to release the write lock & also release the previous read lock.
- When there is exception to acquire locks, the failure case needs to be handled.
- Wrap code in try/finally blocks to ensure freeing resources when exception happens.
ReaderWriterLockSlim lock performs better than old ReaderWriterLock and leads developers to program cleaner designs free of lock recursion. I see downsides to this lock also and that has caused the bug originally.
Seems ReaderWriterLockSlim class wraps the write lock and allows all read programs to read so long as the write lock isn’t held.When write lock is being held, what is outcome of thread that reads? Not certain
To have certainty, we have ReadLock. writelock cannot be taken before read is released. When program tries to hold Read lock, outcome is one of the following tow.
- success, no exception.
- failure, exception is thrown.
The program trying to obtain Readlock operation needs to decide next action based on this outcome. The below program trying to obtain write lock under non-recursive policy leads to exception. .
- EnterReadLock() // read lock is obtained successfully
- EnterWriteLock() // Failed to get get write lock
- ExitWriteLock(); // The write lock is released without being held.