Table of Contents Previous Next
Logo
Threads and Concurrency with C++ : 27.6 Read-Write Recursive Mutexes
Copyright © 2003-2008 ZeroC, Inc.

27.6 Read-Write Recursive Mutexes

The implementation of our read and write operations on page 692 is more conservative in its locking than strictly necessary: only one thread can be in either the read or write operation at a time. However, we have problems with concurrent file access only if we have concurrent writers, or concurrent readers and writers for the same file. However, if we have only readers, there is no need to serialize access for all the reading threads because none of them updates the file contents.
Ice provides a read-write recursive mutex class RWRecMutex (defined in IceUtil/RWRecMutex.h) that implements a reader-writer lock:
namespace IceUtil {

    class RWRecMutex {
    public:
        void readLock() const;
        bool tryReadLock() const;
        bool timedReadLock(const Time&) const;

        void writeLock() const;
        bool tryWriteLock() const;
        bool timedWriteLock(const Time&) const;

        void unlock() const;

        void upgrade() const;
        bool timedUpgrade(const Time&) const;
        void downgrade() const;

        typedef RLockT<RWRecMutex> RLock;
        typedef TryRLockT<RWRecMutex> TryRLock;
        typedef WLockT<RWRecMutex> WLock;
        typedef TryWLockT<RWRecMutex> TryWLock;
    };
}
A read-write recursive mutex splits the usual single lock operation into readLock and writeLock operations. Multiple readers can each acquire the mutex in parallel. However, only a single writer can hold the mutex at any one time (with neither other readers nor other writers being present). A RWRecMutex is recursive, meaning that you can call readLock or writeLock multiple times from the same calling thread.
The member functions behave as follows:
• readLock
This function acquires a read lock. If a writer currently holds the mutex or a thread is waiting for a lock upgrade, the caller is suspended until the mutex becomes available for reading. If the mutex is available, or only readers currently hold the mutex, the call returns immediately with the mutex locked.
• tryReadLock
This function attempts to acquire a read lock. If the lock is currently held by a writer or a thread is waiting for a lock upgrade, the function returns false. Otherwise, it acquires the lock and returns true.
• timedReadLock
This function attempts to acquire a read lock. If the lock is currently held by a writer or another thread is waiting for an upgrade, the function waits for the specified timeout. If the lock can be acquired within the timeout, the function returns true with the lock held. Otherwise, once the timeout expires, the function returns false. (See Section 27.7 for how to construct a timeout value.)
• writeLock
This function acquires a write lock. If readers or a writer currently hold the mutex or another thread is waiting for an upgrade, the caller is suspended until the mutex becomes available for writing. If the mutex is available, the call returns immediately with the lock held.
• tryWriteLock
This function attempts to acquire a write lock. If the lock is currently held by readers or a writer, or if another thread is waiting for an upgrade, the function returns false. Otherwise, it acquires the lock and returns true.
• timedWriteLock
This function attempts to acquire a write lock. If the lock is currently held by readers or a writer, or if another thread is waiting for an upgrade, the function waits for the specified timeout. If the lock can be acquired within the timeout, the function returns true with the lock held. Otherwise, once the timeout expires, the function returns false. (See Section 27.7 for how to construct a timeout value.)
• unlock
This function unlocks the mutex (whether currently held for reading or writing).
• upgrade
This function upgrades a read lock to a write lock. If other readers currently hold the mutex, the caller is suspended until the mutex becomes available for writing. If the mutex is available, the call returns immediately with the lock held.
Only one reader can attempt to upgrade a lock at a time. If several threads call upgrade, all but the first thread receive a DeadlockException.
Note that upgrade is non-recursive. Do not call it more than once from the same thread.
• timedUpgrade
This function attempts to upgrade a read lock to a write lock. If the lock is currently held by other readers, the function waits for the specified timeout. If the lock can be acquired within the timeout, the function returns true with the lock held. Otherwise, once the timeout expires, the function returns false. (See Section 27.7 for how to construct a timeout value.) If another thread is waiting to upgrade the lock, timedUpgrade returns false immediately.
Note that timedUpgrade is non-recursive. Do not call it more than once from the same thread.
• downgrade
This function converts a write lock to a read lock.
As for non-recursive and recursive mutexes, you must adhere to a few rules for correct use of read-write locks:
• Do not call unlock on a mutex unless the calling thread holds the lock.
• You must call unlock as many times as you called readLock or writeLock (or upgrade or successful timedUpgrade) for the mutex to become available to another thread.
• Do not call upgrade or timedUpgrade on a mutex for which you do not hold a read lock.
• upgrade and timedUpgrade are non-recursive (because making them recursive would incur an unacceptable performance penalty). Do not call these methods more than once from the same thread.
• Do not call downgrade on a mutex unless the calling thread holds a write lock.
• You must call downgrade (or unlock) as many times as you called writeLock and upgrade (or successfully called timedUpgrade) for the mutex to become available to another thread.
The implementation of read-write recursive mutexes gives preference to writers: if a writer is waiting to acquire the lock, no new readers are permitted to acquire the lock; the implementation waits until all current readers relinquish the lock and then locks the mutex for the waiting writer. Similarly, the implementation gives preference to a thread that wants to upgrade the lock; no new readers or writers can acquire the lock until the upgrade is complete.
Note that mutexes do not implement any notion of fairness: if multiple writers are continuously waiting to acquire a write lock, which writer gets the lock next depends on the underlying threads implementation. There is no queue of waiting writers to ensure that none of the writers are permanently starved of access to the mutex.
Using a RWRecMutex, we can implement our read and write operations to allow multiple readers in parallel, or a single writer:
#include <IceUtil/RWRecMutex.h>
// ...

namespace Filesystem {
    // ...

    class FileI : virtual public File,
                  virtual public Filesystem::NodeI {
    public:
        // As before...
    private:
        Lines _lines;
        IceUtil::RWRecMutex _fileMutex; // Readwrite mutex
    };
    // ...
}

Filesystem::Lines
Filesystem::FileI::read(const Ice::Current&) const
{
    IceUtil::RWRecMutex::RLock lock(_fileMutex);    // Read lock
    return _lines;
}

void
Filesystem::FileI::write(const Filesystem::Lines& text,
                         const Ice::Current&)
{
    IceUtil::RWRecMutex::WLock lock(_fileMutex);    // Write lock
    _lines = text;
}
This code is almost identical to the non-recursive version on page 686. Note that the only changes are that we have changed the type of the mutex in the servant to RWRecMutex and that we are using the RLock and WLock helpers to guarantee unlocking instead of calling readLock and writeLock directly.
Table of Contents Previous Next
Logo