BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bslmt_readerwriterlock

Detailed Description

Outline

Purpose

Provide a multi-reader/single-writer lock.

Classes

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

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

This section illustrates intended use of this component.

Example 1: Basic 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;
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;
Definition bslstl_map.h:619
Definition bslmt_readerwriterlock.h:294
Definition bsls_timeinterval.h:301

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();
}