18

I'm using read/write locks on Linux and I've found that trying to upgrade a read locked object to a write lock deadlocks.

i.e.

// acquire the read lock in thread 1.
pthread_rwlock_rdlock( &lock );

// make a decision to upgrade the lock in threads 1.
pthread_rwlock_wrlock( &lock ); // this deadlocks as already hold read lock.

I've read the man page and it's quite specific.

The calling thread may deadlock if at the time the call is made it holds the read-write lock (whether a read or write lock).

What is the best way to upgrade a read lock to a write lock in these circumstances.. I don't want to introduce a race on the variable I'm protecting.

Presumably I can create another mutex to encompass the releasing of the read lock and the acquiring of the write lock but then I don't really see the use of read/write locks. I might as well simply use a normal mutex.

Thx

ScaryAardvark
  • 2,855
  • 4
  • 30
  • 43
  • Boost.Thread has the UpgradeLockable concept, but I doubt that's much use to you if your code is already hip-deep in pthreads. – Steve Jessop Mar 09 '10 at 09:55
  • @Steve. Do you know how it's implemented. Does it use a seperate mutex which is what I was considering doing. I suppose I could download it and check it out :o) – ScaryAardvark Mar 09 '10 at 18:03
  • No, I don't know how Boost.Thread does it, sorry. – Steve Jessop Mar 09 '10 at 21:50
  • 1
    @ScaryAardvark (and others who want a summary of the Boost implementation): I just looked at the code for version 1.47.0 of the pthread implementation, and it uses a mutex and 3 condition variables instead of pthread_rwlock_t. Check out boost/thread/pthread/shared_mutex.hpp for details. – BD at Rivenhill Aug 24 '11 at 08:03

4 Answers4

22

What else than a dead lock do you want in the following scenario?

  • thread 1 acquire read lock
  • thread 2 acquire read lock
  • thread 1 ask to upgrade lock to write
  • thread 2 ask to upgrade lock to write

So I'd just release the read lock, acquire the write lock and check again if I've to make the update or not.

AProgrammer
  • 51,233
  • 8
  • 91
  • 143
  • Why would one of the thread get the write lock if the other is holding a read lock? And if both finally can get the write lock, the second one would have the problem that all checks that have been made have to be redone, as the protected resource may have been changed in significant way. Releasing and reacquiring thus provides the same behavior. – AProgrammer Mar 09 '10 at 09:12
  • I was hoping to avoid the second check on the object being locked. I know that IBM's implementation of read/write locks allows a calling thread to upgrade it's lock if it's the only thread holding a write lock. http://publib.boulder.ibm.com/infocenter/iseries/v5r4/index.jsp?topic=/apis/users_93.htm – ScaryAardvark Mar 09 '10 at 09:16
  • @nos. Yes, this would deadlock. T1 when asking to upgrade would be blocked from doing so as T2 has a read lock. T2 would then block when upgrading to write lock as T1 holds a read lock. – ScaryAardvark Mar 09 '10 at 09:24
  • 6
    in answer to your question "what do you expect", the answer is that if r/w locks are upgradeable then something like: thread 1's attempt to upgrade blocks, then thread 2's attempt fails. Thread 2 responds to this by releasing the reader lock and trying to re-acquire it and start all over again. Once it releases, Thread 1 can gain the writer lock, completes its transaction, and drops the lock. Anyone trying to get the reader lock while Thread1 was waiting on the writer lock, blocks until Thread1 releases both. Optimistic locking, sort of. – Steve Jessop Mar 09 '10 at 22:26
  • FWIW, I have written an upgradeable reader writer lock class from scratch. "Upgrade" either has to be a non-blocking "Try" function which can fail or it has to be able to "unlock" and then relock (which can block and can also be considered a fail case). – Adisak Apr 29 '13 at 19:26
  • The problem with upgrading read locks to write locks, is if there are multiple threads trying to do this and say if you fail to acquire the write lock, you reenter trying to get another read lock, is that this can cause a livelock, or a long lock convoy. How do you resolve this? – CMCDragonkai Jul 08 '15 at 09:25
3

The pthread library does not support this operation directly.

As a workaround, you can define a mutex to protect the lock:

  • To acquire the lock, first acquire the mutex, then the lock (read or write as needed), then release the mutex. (Never acquire the lock without holding the mutex.)
  • To release the lock, just release it (no mutex required here).
  • To upgrade the lock, acquire the mutex, release the read lock, acquire the write lock, then release the mutex.
  • To downgrade the lock, acquire the mutex, release the write lock, acquire the read lock, then release the mutex.

This way, no other thread can snatch the write lock while you are trying to upgrade it. However, your thread will block if other threads are holding the read lock when you try to upgrade.

Also, as mentioned above, if two threads are trying to upgrade the same lock at the same time you will encounter a deadlock:

  • T1 and T2 both hold a read lock.
  • T1 wishes to upgrade, acquires the mutex, releases the read lock and tries to acquire the write lock. This is blocked by T2.
  • T2 wishes to upgrade, tries to acquire the mutex and is blocked by T1.

Takeaway from my CS lectures: Deadlocks cannot be reliably avoided. For every strategy proposed, there is at least one use case in which the strategy is impractical. The only thing you can do is detect deadlock conditions (i.e. if a call fails with EDEADLK) and make sure your code is prepared to deal with that situation. (How to recover depends heavily on your code.)

Downgrading in this manner is not prone to deadlocks¹, although a downgrade and a concurrent upgrade can deadlock. If only one of your threads upgrades that lock in this manner (and other threads get a write lock immediately if needed), there is also no risk of deadlock¹.

As others have said, acquiring a write lock immediately when you might need it would be an alternative which is not prone to deadlocking¹, but might unnecessarily prevent other read operations from taking place concurrently.

Conclusion: It depends on your code.

If the read-only phase is brief (i.e. brief enough so you can afford blocking other read operations during that time), then I would go for the gratuitous write lock approach.

If the read-only phase may last long and blocking other reads during that time is unacceptable, go for the mutex-protected lock upgrade, but either limit it to one thread per lock (“only T1 may upgrade lock L42, but not other threads”) or provide a way to detect and recover from deadlocks.


¹ Unless resources other than this lock and its mutex come into play

user149408
  • 5,385
  • 4
  • 33
  • 69
1

Easiest and safest would be to take the write-lock from the moment you could want to change your data instead of from the moment you're sure you will change it. I know that this will make access to your data a bit more serialized.

I was a bit surprised when reading this question, because I never even considered first taken a read-lock and then upgrading to a write-lock. Well, different situation could need different approaches.

stefaanv
  • 14,072
  • 2
  • 31
  • 53
  • 2
    It seems a bit over the top to acquire a write lock when you may not need it. This would serialise, as you pointed out. Whereas acquiring a read lock won't block. At the point you know you need to change something, you can upgrade to a write lock. It's just the actual act of doing so, under posix, introduces a race, which is a shame. – ScaryAardvark Mar 09 '10 at 09:21
  • It is a shame, but in most cases I know of, it is more a theoretical difference. – stefaanv Mar 09 '10 at 10:26
0

I think instead of using pthread read/write lock, you can use Posix fcntl() . Here you can upgrade from read to write without any hassle. We are using it for B-tree insertion. Once we come to know the node where insertion happen , we will upgrade it to write lock. Also when we need to split the node we will upgrade the lock of the node, its parent node and children from read to write. Since B-tree is a file based Data structure , it helps to take a lock on region of file.