Boost logo

Boost :

Subject: Re: [boost] [thread] On shared_mutex
From: Howard Hinnant (howard.hinnant_at_[hidden])
Date: 2010-11-30 09:49:59


On Nov 29, 2010, at 10:28 PM, Marsh Ray wrote:

> The facility I used did not use have a different lock type for 'upgradable', all shared locks were upgradeable (of course we called them read and write locks).
>
> An upgrade operation would always succeed if you were willing to wait long enough. The way the deadlock issue was handled was simple: it didn't guarantee that the upgrade would be performed atomically in every case. Sometimes a different thread would get his exclusive ownership in between your shared and exclusive locks. Of course, whether or not your upgrade happened atomically was indicated in the return value.
>
> This allowed a common pattern:
>
> shareable_mutex g_mut;
>
> // Ensure color is made blue, if it isn't already.
> // get_color() and set_color() are nontrivial operations.
> void ensure_blue()
> {
> lock l(g_mut, shared, forever);
> if (get_color() != blue)
> {
> bool upgrade_was_atomic = l.upgrade_to_exclusive(forever);
> if (upgrade_was_atomic || get_color() != blue)
> set_color(blue);
> }
> }
>
> So the code didn't need to call get_color() twice when the lock was upgraded atomically. That was the usual case. I'm sure there's similar code with your interface.
>
> There were some things I liked about this pattern:
> * The code is simple. There's only one lock. The code doesn't have error conditions to test for, or exceptions being thrown, unless something else is broken.
> * There is one condition that must be checked, but even if you forgot to check it, it was probably just a missed optimization. You still had an exclusive lock at that point.
> * There was no such thing as a 'lock' object that wasn't actually locking a mutex.
>
> Interestingly:
> * This avoids a context switch over the "upgrade fails, calling code loops and tries again" methods. The thread asks for an upgrade and he doesn't resume execution until he has an exclusive.
>
> These days I probably wouldn't block forever on a single lock acquisition like that. I'm always waiting on at least two objects, one of them being a global event to signal application shutdown.

Thanks for sharing your experience. It helps to know that others have needed and successfully used ownership converting mutexes.

For completeness for everyone I've translated your example to ting::syntax:

ting::upgrade_mutex g_mut;

// Ensure color is made blue, if it isn't already.
// get_color() and set_color() are nontrivial operations.
void ensure_blue()
{
  ting::shared_lock<ting::upgrade_mutex> l(g_mut);
  if (get_color() != blue)
  {
      std::unique_mutex<ting::upgrade_mutex> lk(std::move(l), std::try_to_lock);
      if (lk.owns_lock())
          set_color(blue); // upgrade is atomic
      else
      {
           l.unlock();
           lk.lock(); // upgrade is not atomic
           if (get_color() != blue)
               set_color(blue);
      }
  }
}

I believe it would be easy to build a user-defined lock type which took a ting::upgrade_mutex which had exactly the syntax and semantics of your lock. One would essentially put the logic I have in the outer if() statement inside the conversion function:

class lock
{
    ting::upgrade_mutex g_mut;
    ...
};

bool
lock::upgrade_to_exclusive()
{
    if (g_mut.try_unlock_shared_and_lock())
        return true;
    g_mut.unlock_shared();
    g_mut.lock();
    return false;
}

If you wanted a timed version you could:

template <class Rep, class Period>
bool
lock::upgrade_to_exclusive(std::chrono::duration<Rep, Period> t)
{
    if (g_mut.try_unlock_shared_and_lock_for(t))
        return true;
    g_mut.unlock_shared();
    g_mut.lock();
    return false;
}

You could not write any of the above 3 functions with boost::shared_mutex.

-Howard


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk