I was wondering what are the advantages of using a upgradable read lock as opposed performing these steps:
- Take read lock
- Check condition to see if we need to take write lock
- Release Read Lock
- Take Write Lock
- Perform update
- Release Write Lock
One apparent disadvantage of the performing the above steps as opposed taking an upgradable read lock is, that there is an window of time between the steps 3 and 4, where another thread can take up a write lock.
Apart from this advantage what other advantages do you find for taking upgradable read lock over the steps I have mentioned above?
A readers/writer lock regulates access to a set of data. The readers/writer lock is so called because many threads can hold the lock simultaneously for reading, but only one thread can hold the lock for writing. Most device drivers do not use readers/writer locks. These locks are slower than mutexes.
Write-preferring RW locks avoid the problem of writer starvation by preventing any new readers from acquiring the lock if there is a writer queued and waiting for the lock; the writer will acquire the lock as soon as all readers which were already holding the lock have completed.
ReaderWriterLockSlim allows multiple threads to be in read mode, allows one thread to be in write mode with exclusive ownership of the lock, and allows one thread that has read access to be in upgradeable read mode, from which the thread can upgrade to write mode without having to relinquish its read access to the ...
Let's consider the different ways in which one can use a reader-writer lock that doesn't have a separate "upgradable reader".
With your pattern, there is a race between step 3 and 4 as you point out, where another thread can take the writer lock. More to the point, there is a step between 3 and 4 where a thread can take the writer lock and change the state we observed in step 2.
Therefore, we've got four choices depending on how likey this is to happen:
We stay with your approach because this is actually impossible (e.g. a given state transition is one-way in our application, so once observed it is permanent). In this case though we could quite possibly have remodelled so as to not need a lock at all. (One-way transitions lend themselves to lock-free techniques).
We just take the writer lock in the first place, because the state we observe in step 2 is very likely to change and it's a waste of time checking it with a reader lock.
We change your steps to:
We change to:
It's not hard to see why 4 was more tempting to some, though it is only slightly harder to see how it makes deadlocks easy to create. Sadly, that slightly harder is just enough for a lot of people to see the advantages without seeing the disadvantages.
For anyone who doesn't spot it, if two threads have a read lock and one of them upgrades to a write lock it must wait for the other to release the read-lock. However if that second thread upgrades to a write lock without releasing the read-lock then it will wait forever on the first thread, which will wait forever on it.
As said above, just which approach is best depends on how likely it is for the state to change in the meantime (or how promptly we want to react to it, I suppose). Even the last approach with the non-releasing upgrade can have its place in viable code, as long as there can only ever be one thread that ever tries to upgrade its lock without releasing.
Aside from the special case where the last option works, the difference between the other options are all about performance, and which is most performant mostly depends on the cost of re-checking the state and the likelihood of the write being aborted due to a change in the meantime.
However, note that all of them involve taking a writer lock, so all of them have the effect of blocking all reading threads, even when the write is indeed aborted.
Upgradable read-locks give us a middle-ground because while they block write-locks and other upgradable read-locks, they don't block read locks. They are perhaps better though of not as read-locks that can upgrade as a write-locks that have yet to commit to writing.* In the cases where it was decided not to upgrade, the effect on the reading threads is nil.
This means that if it's even slightly possible that the thread will decide not to change the state, the reading threads are not affected, and the performance improvement can certainly justify it's use.
*For that matter, "reader-writer" is a bit of a misnomer, we could e.g. protect an array of ints or objects with a ReaderWriterLockSlim
, use the read-locks for both reading and writing individual items atomically and use the write-locks for operations that need to read the entire array without parts of it changing as it reads. In such a case it's a reading operation than needs the exclusive lock, while writing operations are fine with the shared lock.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With