Boost logo

Boost :

From: Howard Hinnant (hinnant_at_[hidden])
Date: 2004-07-09 17:42:37


I'm wondering about the following scenario (with whatever syntax):

try_mutex m1;
rw_mutex rwm;

void foo()
{
     rw_mutex::upgradable_read_lock ur(rwm);
     ...
     if (...)
     {
          rw_mutex::write_lock w(m, false);
          // Need to write lock rwm and lock m1 here (for whatever
reason)
     }
}

This implies a use of lock_both, which requires two try_locks. I can
build a generic try_lock to handle transferring from ur to w like so:

template <class TryLock1, class TryLock2>
class transfer_lock
{
public:

     transfer_lock(TryLock1& m1, TryLock2& m2, bool lock_it = true);

     ~transfer_lock();

     void lock();
     bool try_lock();
     void unlock();
     bool locked() const;
     operator int bool_type::* () const;
private:
     transfer_lock(const transfer_lock&);
     transfer_lock& operator=(const transfer_lock&);
};

This is just an extension of the helper lock class I presented earlier
with try_lock functionality added. transfer_lock::try_lock() will need
to try to transfer ownership from m2 (the upgradable_read_lock) to m1
(the write_lock) in a non-blocking manner. This will require some
additional interface on upgradable_read_lock, possibly:

template <class RW_Mutex>
class upgradable_read_lock
{
public:
     ...
     bool try_transfer_to(write_lock<mutex_type>& w);
     ...
};

So the end user code might look like:

try_mutex m1;
rw_mutex rwm;

void foo()
{
     typedef rw_mutex::upgradable_read_lock UR;
     typedef rw_mutex::write_lock WL;
     typedef transfer_lock<WL,UR> TL;
     typedef try_mutex::lock OtherLock;
     UR ur(rwm);
     ...
     if (...)
     {
          WL w(m, false);
          TL tl(w, ur, false);
          OtherLock l1(m1, false);
          lock_both<TL, OtherLock> lock(tl, l1);
          // ok, ur unlocked, w locked, and l1 locked, all atomically
     }
}

Questions:

1. Does anyone see a disaster (deadlock or otherwise) in trying to do
all this atomically? I so far haven't seen any problems, but that
doesn't mean much.

2. If there are no problems, does anyone see this use scenario as
likely to be needed, or worth the trouble?

Notes:

This begs the question of adding:

template <class RW_Mutex>
class read_lock
{
public:
     ...
     bool try_transfer_to(write_lock<mutex_type>& w);
     ...
};

However I strongly recommend against it. Substituting read_lock in for
upgradable_read_lock in the previous code:

try_mutex m1;
rw_mutex rwm;

void foo()
{
     typedef rw_mutex::read_lock RL;
     typedef rw_mutex::write_lock WL;
     typedef transfer_lock<WL,RL> TL;
     typedef try_mutex::lock OtherLock;
     RL r(rwm);
     ...
     if (...)
     {
          WL w(m, false);
          TL tl(w, r, false);
          OtherLock l1(m1, false);
          lock_both<TL, OtherLock> lock(tl, l1);
          // error, deadlock possible!
     }
}

If two threads try to construct the lock_both simultaneously (which
isn't possible with the upgradable_read_lock version) then neither will
be able to obtain the write_lock w because each will be holding the
read_lock r and refusing to let it go.

Therefore I believe the try_transfer_to(write_lock) functionality for
read_lock is inherently dangerous and shouldn't be provided.

Comments?

-Howard


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