// bslmt_readerwriterlock.h -*-C++-*- // ---------------------------------------------------------------------------- // NOTICE // // This component is not up to date with current BDE coding standards, and // should not be used as an example for new development. // ---------------------------------------------------------------------------- #ifndef INCLUDED_BSLMT_READERWRITERLOCK #define INCLUDED_BSLMT_READERWRITERLOCK #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide a multi-reader/single-writer lock. // //@CLASSES: // bslmt::ReaderWriterLock: multi-reader/single-writer lock class // //@SEE_ALSO: bslmt_readerwritermutex, bslmt_readlockguard, // bslmt_writelockguard, bslmt_readerwriterlockassert // //@DESCRIPTION: This component defines an efficient multi-reader/single-writer // lock (RW-Lock) mechanism, 'bslmt::ReaderWriterLock'. It is designed to // allow concurrent *read* access to a shared resource while still controlling // *write* access. // // RW-Locks are generally used for resources which are frequently read and less // frequently updated. Unlike other lock mechanisms (e.g.,"Mutexes"), RW-Locks // provide two distinct but mutually exclusive lock states: a *read* *lock* // state, and a *write* *lock* state. Multiple callers can simultaneously // acquire a *read* *lock*, but only one *write* *lock* may be active at any // given time. // // This implementation gives preference to writers, which can lead to reader // "starvation" in applications with continuous writes. // ///Comparison between 'bslmt' Reader-Writer Locks /// - - - - - - - - - - - - - - - - - - - - - - - //: o 'bslmt::ReaderWriterLock' (defined in this component). Preferred only //: when very long hold times are anticipated. Provides 'upgrade*' methods //: from a locked-for-read state to a locked-for-write state, but the use of //: this feature is discouraged as it has performed poorly on benchmarks. //: //: o 'bslmt::ReaderWriterMutex': Preferred for most use-cases. Doesn't offer //: the 'upgrade' function, but performs better than //: 'bslmt::ReaderWriterLock' under most conditions. //: //: o 'bslmt::RWMutex': Deprecated. // // Note that for extremely short hold times and very high concurrency, a // 'bslmt::Mutex' might outperform all of the above. // // Also note that lock guards are provide by 'bslmt_readlockguard' and // 'bslmt_writelockguard'. // // Also note that asserts to verify locking are provided by // 'bslmt_readerwriterlockassert'. // ///Read and Write Locks ///-------------------- // If a *read* *lock* is attempted while another *read* *lock* is already // active and there are no pending *write* *locks*, the lock will be // immediately granted. If there are pending or active *write* *locks*, the // reader will block until all *write* *locks* (including those acquired after // the reader) are released. // // 'bslmt::ReaderWriterLock' also supports atomic conversion from *read* to // *write* *locks*. This feature allows callers to first acquire a *read* // *lock*, determine if a write operation needs to be performed, and // conditionally upgrade to a *write* *lock* without possibility of another // writer having changed the state of the resource. // // The component supports both optimistic and pessimistic lock conversions. // ///Optimistic Lock Conversions ///- - - - - - - - - - - - - - // Any basic *read* *lock* can be converted to a *write* *lock*, but the // conversion is not guaranteed to be atomic. If the conversion cannot be // performed atomically, which means the lock was first released, then a lock // for write was acquired again (possibly after other threads have obtained and // released a write lock themselves), the state of the resource must be // re-evaluated, since the resource may have been changed by another thread. // ///Pessimistic Lock Conversions /// - - - - - - - - - - - - - - // For conditions with high probably for write contention, or where the cost of // re-evaluating the update condition is too high, clients may choose to // acquire a *read* *lock* that is guaranteed to upgrade atomically, that is // without the possibility of another thread acquiring a read or write lock in // the meantime. The 'lockReadReserveWrite' method allows a caller to acquire // a *read* *lock* and simultaneously reserve a *write* *lock*. The *read* // *lock* can then be atomically upgraded to the reserved *write* *lock* by // calling the 'upgradeToWriteLock' method. // ///Usage ///----- // The following snippet of code demonstrates a typical use of a reader/writer // lock. The sample implements a simple cache mechanism for user information. // We expect that the information is read very frequently, but only modified // when a user "badges" in or out, which should be relatively infrequent. //.. // struct UserInfo{ // long d_UserId; // char d_UserName[MAX_USER_NAME]; // char d_badge_location[MAX_BADGE_LOCATION]; // int d_inOutStatus; // bsls::TimeInterval d_badgeTime; // }; // // class UserInfoCache { // typedef bsl::map<int, UserInfo> InfoMap; // // bslmt::ReaderWriterLock d_lock; // InfoMap d_infoMap; // // public: // UserInfoCache(); // ~UserInfoCache(); // // int getUserInfo(int userId, UserInfo *userInfo); // int updateUserInfo(int userId, UserInfo *userInfo); // int addUserInfo(int userId, UserInfo *userInfo); // void removeUser(int userId); // }; // // inline // UserInfoCache::UserInfoCache() // { // } // // inline // UserInfoCache::~UserInfoCache() // { // } // // inline // int UserInfoCache::getUserInfo(int userId, UserInfo *userInfo) // { // int ret = 1; //.. // Getting the user info does not require any write access. We do, however, // need read access to 'd_infoMap', which is controlled by 'd_lock'. (Note // that writers *will* block until this *read* *lock* is released, but // concurrent reads are allowed.) The user info is copied into the // caller-owned location 'userInfo'. //.. // d_lock.lockRead(); // InfoMap::iterator it = d_infoMap.find(userId); // if (d_infoMap.end() != it) { // *userInfo = it->second; // ret = 0; // } // d_lock.unlock(); // return ret; // } // // inline // int UserInfoCache::updateUserInfo(int userId, UserInfo *userInfo) // { // int ret = 1; //.. // Although we intend to update the information, we first acquire a *read* // *lock* to locate the item. This allows other threads to read the list while // we find the item. If we do not locate the item we can simply release the // *read* *lock* and return an error without causing any other *reading* thread // to block. (Again, other writers *will* block until this *read* *lock* is // released.) //.. // d_lock.lockRead(); // InfoMap::iterator it = d_infoMap.find(userId); // if (d_infoMap.end() != it) { //.. // Since 'it != end()', we found the item. Now we need to upgrade to a *write* // *lock*. If we can't do this atomically, then we need to locate the item // again. This is because another thread may have changed 'd_infoMap' during // the time between our *read* and *write* locks. //.. // if (d_lock.upgradeToWriteLock()) { // it = d_infoMap.find(userId); // } //.. // This is a little more costly, but since we don't expect many concurrent // writes, it should not happen often. In the (likely) event that we do // upgrade to a *write* *lock* atomically, then the second lookup above is not // performed. In any case, we can now update the information and release the // lock, since we already have a pointer to the item and we know that the list // could not have been changed by anyone else. //.. // if (d_infoMap.end() != it) { // it->second = *userInfo; // ret = 0; // } // d_lock.unlock(); // } // else { // d_lock.unlock(); // } // return ret; // } // // inline // int UserInfoCache::addUserInfo(int userId, UserInfo *userInfo) // { // d_lock.lockRead(); // bool found = !! d_infoMap.count(userId); // if (! found) { // if (d_lock.upgradeToWriteLock()) { // found = !! d_infoMap.count(userId); // } // if (! found) { // d_infoMap[userId] = *userInfo; // } // d_lock.unlock(); // } // else { // d_lock.unlock(); // } // return found ? 1 : 0; // } // // inline // void UserInfoCache::removeUser(int userId) // { // d_lock.lockWrite(); // d_infoMap.erase(userId); // d_lock.unlock(); // } //.. #include <bslscm_version.h> #include <bsls_atomic.h> #include <bsls_atomicoperations.h> #include <bslmt_condition.h> #include <bslmt_mutex.h> #include <bslmt_threadutil.h> namespace BloombergLP { namespace bslmt { // ====================== // class ReaderWriterLock // ====================== class ReaderWriterLock { // This class provides a multi-reader/single-writer lock mechanism. // PRIVATE TYPES enum SignalState { e_NOT_SIGNALED = 0, e_WRITE_SIGNALED = 1, e_UPGRADE_SIGNALED = 2 }; // CLASS DATA static const long long WRITER_MASK = 0x000000000000FFFFLL; // mask for writer portion of the counter static const long long READER_MASK = 0x00000000FFFF0000LL; // mask for active reader potion of the counter static const long long READER_INC = 0x0000000000010000LL; // value used to increment and decrement the active reader count by 1 static const long long BLOCKED_READER_MASK = 0x0000FFFF00000000LL; // mask for waiting reader portion of the counter static const long long BLOCKED_READER_INC = 0x00000000100000000LL; // value used to increment and decrement the blocked reader count by 1 static const long long READ_OK = 0x0001000000000000LL; // mask for read ok flag which indicates that readers can acquire a // lock without blocking. static const long long UPGRADE_PENDING = 0x0002000000000000LL; // mask for upgrade pending flag which indicates that a reader thread // is waiting to upgrade to a writer static const long long RESERVATION_PENDING = 0x0004000000000000LL; // mask for reservation pending flag static const long long READ_BCAST_MASK = 0xFFF0000000000000LL; // mask for the read broadcast state portion of counter static const long long READ_BCAST_INC = 0x0010000000000000LL; // value used to increment the read broadcast count by 1 // INSTANCE DATA bsls::AtomicOperations::AtomicTypes::Int64 d_rwCount; // atomic counter used to track active // and waiting read/write lock // requests Mutex d_mutex; // used for access control Condition d_readCond; // used to signal waiting readers Condition d_writeCond; // used to signal waiting writers Condition d_upgradeCond; // used to signal upgraders bsls::AtomicUint64 d_owner; // id of thread that currently owns // this lock if it is in the write // lock state, or the id of the thread // that holds the write reservation if // one exists volatile SignalState d_signalState; bsls::AtomicBool d_owned; // NOT IMPLEMENTED ReaderWriterLock(const ReaderWriterLock&); ReaderWriterLock& operator=(const ReaderWriterLock&); public: // CREATORS ReaderWriterLock(); // Construct a reader/writer lock initialized to an unlocked state. ~ReaderWriterLock(); // Destroy this reader/writer lock. // MANIPULATORS void lockRead(); // Lock this reader/writer lock for read. If there are no pending or // active write locks, the call will return immediately, otherwise it // block until all write locks have been released. Use 'unlock' to // release the lock. void lockReadReserveWrite(); // Lock this reader/writer lock for read and reserve a write lock so // that a call to 'upgradeToWriteLock' is guaranteed to upgrade // atomically. If there are no pending or active write locks, the call // will return immediately, otherwise it will block until it all active // and pending writes have completed. The lock may then be atomically // converted to a write lock by calling 'upgradeToWriteLock'. Use // 'unlock' to release the lock. void lockWrite(); // Lock the reader/writer lock for write. The call will block until // all active read locks or active/pending write locks are released. // When the reader/writer lock is locked for write, all read/write lock // attempts will either fail or block until the lock is released. Use // 'unlock' to release the lock. int tryLockRead(); // Attempt to lock this reader/writer lock for read. Return 0 on // success, and a non-zero value if the lock is currently locked for // write or if there are writers waiting for this lock. If successful, // 'unlock' must be used to release this lock. int tryLockWrite(); // Attempt to lock this reader/writer lock for write. Return 0 on // success, and a non-zero value if the lock already locked for read or // write. If successful, 'unlock' must be called to release the lock. int upgradeToWriteLock(); // Convert a read lock (acquired by a successful call to 'lockRead', // 'lockReadReserveWrite', or 'tryLockRead') to a write lock. Return 0 // if the upgrade operation was atomic, and a non-zero value otherwise. // If there are other active read locks, the call will block until all // current read locks are released. Note that locks that were acquired // through 'lockReadReserveWrite' are guaranteed to upgrade atomically. // Use 'unlock' to release this lock. int tryUpgradeToWriteLock(); // Attempt to atomically convert a read lock (acquired by a successful // call to 'lockRead', 'lockReadReserveWrite', or 'tryLockRead') to a // write lock. Return 0 on success, and a non-zero value otherwise. // If a write lock request is already pending, a non-zero value is // immediately returned. If there are other active read locks, the // call will block until all current read locks are released. Note // that locks that were acquired through 'lockReadReserveWrite' are // guaranteed to succeed. void unlockRead(); // *DEPRECATED* Use 'unlock' instead. Note that calls to this function // are simply forwarded to 'unlock'. void unlockReadUnreserveWrite(); // *DEPRECATED* Use 'unlock' instead. Note that calls to this function // are simply forwarded to 'unlock'. void unlockWrite(); // *DEPRECATED* Use 'unlock' instead. Note that calls to this function // are simply forwarded to 'unlock'. void unlock(); // Release a read lock that was previously acquired from a successful // call to 'lockRead', 'lockReadReserveWrite', or 'tryLockRead', or a // call to write lock which was previously acquired by a successful // call to 'lockWrite', 'tryLockWrite', or 'upgradeToWriteLock'. Note // that the behavior is undefined unless the calling thread currently // owns this read/write lock. // ACCESSORS bool isLocked() const; // Return 'true' if this reader-write lock is currently read locked or // write locked, and 'false' otherwise. bool isLockedRead() const; // Return 'true' if this reader-write lock is currently read locked, // and 'false' otherwise. bool isLockedWrite() const; // Return 'true' if this reader-write lock is currently write locked, // and 'false' otherwise. }; } // close package namespace // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // ---------------------- // class ReaderWriterLock // ---------------------- // CREATORS inline bslmt::ReaderWriterLock::ReaderWriterLock() : d_signalState(e_NOT_SIGNALED) , d_owned(false) { bsls::AtomicOperations::initInt64(&d_rwCount, READ_OK); } inline bslmt::ReaderWriterLock::~ReaderWriterLock() { } // MANIPULATORS inline void bslmt::ReaderWriterLock::unlockRead() { unlock(); } inline void bslmt::ReaderWriterLock::unlockReadUnreserveWrite() { unlock(); } inline void bslmt::ReaderWriterLock::unlockWrite() { unlock(); } // ACCESSORS inline bool bslmt::ReaderWriterLock::isLocked() const { bsls::Types::Int64 rwcount = bsls::AtomicOperations::getInt64(&d_rwCount); if (rwcount & (READER_MASK|WRITER_MASK)) { return true; // RETURN } else { return false; // RETURN } } inline bool bslmt::ReaderWriterLock::isLockedRead() const { bsls::Types::Int64 rwcount = bsls::AtomicOperations::getInt64(&d_rwCount); if (rwcount & READER_MASK) { return true; // RETURN } else { return false; // RETURN } } inline bool bslmt::ReaderWriterLock::isLockedWrite() const { bsls::Types::Int64 rwcount = bsls::AtomicOperations::getInt64(&d_rwCount); if (rwcount & WRITER_MASK) { return true; // RETURN } else { return false; // RETURN } } } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2015 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------