The implementation of our read and
write operations on
page 800 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.
Note that upgrade is non-recursive. Do not call it more than once from the same thread.
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 31.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.
•
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; // Read‑write 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 794. 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.