6

I am updating a code that previously uses its own read and write lock mechanism (this code was written prior to C++11 and std::shared_mutex does not exist yet) to use the std version of C++.

There is two classes named ReadLock and WriteLock, and ReadLock can be upgraded to WriteLock using method call. WriteLock can also be obtained anytime without upgrade from shared_lock. Reading the C++ std version of shared_lock, i think its straight forward and easy.

ReadLock will just be replaced with shared_lock WriteLock will be replaced with unique_lock and can be triggered anytime calling lock() to obtain a Writelock Upgrade to Writelock will just be performed in two steps unlocking shared_lock and locking unique_lock

My problem now, is when I read thru discussions, there might be specific problems and thread like this raised a concern (thread from Howard Hinnant) Can a shared lock on a std::shared_timed_mutex be upgraded to an exclusive lock?

So I consider using the boost version instead because it supports upgrade_lock and boost::upgrade_to_unique_lock, but i am confuse on how to approach the design on it.

WriteLock class can both represent unique_lock and/or upgrade_lock and upgrade_to_unique_lock objects since as I mentioned above, WriteLock can be attain without shared_lock as well.

Also, I am confuse on how to explicitly trigger lock() and unlock() mechanism if I use upgrade_lock from boost; boost::upgrade_lock has a constructor that accepts defer_lock as parameter and has methods of lock() and unlock() that i can use anytime, but i dont see the same functioanlity if we upgrade it to unique lock using boost::upgrade_to_unique_lock(), creating an object of upgrade_to_unique_lock() automatically converts and lock it.

here is a pseudocode of what I am trying to attain approach 1 basic operation

    void foo()
    {
        ReadLock readLock = someWrapper->AcquireReader();    // return a shared lock
        ...
        WriteLock writeLock = readLock->UpgradeToWriter();   // returns a unique lock
        writeLock->Lock();
        // do something here
    }   // writelock are unlock since constructor is called

approach 2 straight up writelock

    void foo()
    {
        WriteLock writeLock = someWrapper->GetWriter();   // acquire lock straight
        writeLock->Lock();
        // do something here
    }   // writelock are unlock since constructor is called
}

so my questions here are

  1. Is my concern if I use std C++ valid that i need to use boost instead or is C++ std::shared_mutex is enough? In the above sample both UpgradeToWriter() and GetWriter() will just return an object with unique_lock, but Upgrade will just unlock the shared_lock first.
  2. If I use boost version, how should I approach the design as upgrade_to_unique_lock is different type to unique_lock? I was thinking that the WriteLock class both contains definitions of unique_lock, upgrade_lock and boost:upgrade_to_unique_lock and will be locked whether its an upgrade or just a plain get that brings me to my last and final question
  3. How to explicitly or defered/trigger lock for boost:upgrade_to_unique_lock? as it does not have the defer_lock parameter nor the lock() and unlock() method, just the constructor to do his stuff?

from boost sample on usage of boost::upgrade_to_unique_lock , all samples looks like the below code, creating an object automaticaly triggers conversion and lock.

void writer()
{
  // get upgradable access
  boost::upgrade_lock<boost::shared_mutex> lock(_access);

  // get exclusive access
  boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock); // <-- want this to be performed or triggered explicitly, how?
  // now we have exclusive access
}

Thank you in advance, I know there are C++ experts in here that can help a brother out

user2300947
  • 450
  • 4
  • 14
  • 1
    What do you want to happen if two readers both try to upgrade their locks at the same time? Or do you want a special "ungradable lock" that only one reader can have at a time? – David Schwartz Jan 13 '20 at 05:32
  • Sane libraries with upgrade have 3 locks: read, upgradable and write. At most one (up/write) can exist. Any number of reads can exist with one up. Upgrading to write blocks all new readers and waits for existing readers to finish, then becomes a write lock. It appears that the system the OP hand wrote doesn't do this? It appears boost does. Is that your problem, OP? – Yakk - Adam Nevraumont Jan 13 '20 at 05:38
  • @DavidSchwartz writer lock should be exclusive so if both try to upgrade, only one should pass. I still want multiple readers using shared_lock, I may have to use upgrade_lock if its time to switch to unique lock and that's it. I dont see the use of "upgradable lock" that only allows one reader if it is not going to be upgraded to exclusive afterwards, I dont realy understand the use case of upgrade lock with only one reader at this point, care to help me out? – user2300947 Jan 13 '20 at 07:06
  • @user2300947 How can one pass if write lock is exclusive and the other one holds a read lock? See my "answer" which isn't really an answer. – David Schwartz Jan 13 '20 at 07:16

1 Answers1

6

Here's what you're missing:

  1. Two threads each have an upgradable read lock.
  2. Both threads try to upgrade their read lock to a write lock.
  3. Neither thread can make forward progress until all other read locks are released.
  4. Both threads hold a read lock.

This is a deadlock. So you have two choices:

  1. Ensure this situation can never happen: Only one thread can hold an upgradable read lock at a time.
  2. Break the deadlock: When you try to upgrade a read lock, the result may be to fail the operation and require the thread to release the read lock before it attempts to acquire a write lock.
David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • thanks, I also read http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3427.html and now the justification for update_lock is understood, my other concern now if I will use boost is how to trigger the convert to unique lock thru boost::upgrade_to_unique_lock explicitly most of the samples and code I see online declares it locally and it automatically locks in that scope. – user2300947 Jan 13 '20 at 07:31