Howard E. Hinnant
Ion Gaztañaga (igaztanaga at gmail dot com)
2006-08-01

C++ mutexes and locks

This document outlines a design for mutexes and locks in C++0X. It does not mention, condition variables, threads or other useful multithreading tools, not because I don't think they're important, but just because it is beyond the scope of this document. This document restricts itself only to mutexes and locks.

This is a derivative work of that done in boost. Notable differences between this and the boost locks/mutexes include:

Contents

Also see performance testing.

Infrastructure

Exception class:

class lock_error
    : public std::exception
{
public:
    virtual const char* what () const throw();
};

class thread_resource_error
    : public std::exception
{
public:
    virtual const char* what () const throw();
};

Instead of enums, type-tags are used to let the constructor know it should defer or try-lock. See lock descriptions for more details.

namespace detail
{
struct defer_lock_type {};
struct try_lock_type {};
struct accept_ownership_type {};
}
 
extern detail::defer_lock_type       defer_lock;
extern detail::try_lock_type         try_to_lock;
extern detail::accept_ownership_type accept_ownership;

Mutex Concepts

There are a set of four mutex concepts, each a refinement of the previous. Notably absent from all of these concepts is Movable or Copyable.

  1. Exclusive ownership
  2. Shared ownership
  3. Convertible shared ownership
  4. Upgradable ownership

The implementation provides the following types which meet these concepts:

  1. mutex
  2. sharable_mutex
  3. convertible_shared_mutex
  4. upgradable_mutex
  5. null_mutex

It is unspecified whether all of the above mutex types are actually different types, or simply typedef's to a smaller set of common types.

The null_mutex models the upgradable ownership concept, and simply immediately returns for each call (with a value of true for the try-locks). This type can be useful for types which take a "synchronization policy" as a template parameter.

Exclusive ownership

This is the base mutex concept which only models mutually exclusive ownership. It has five required signatures. Notably absent from this requirement is that of CopyConstructible, CopyAssignable, MoveConstructible and MoveAssignable. Implementations are encouraged to supply additional implementation defined constructors which allow setting various OS dependent mutex attributes.

The exception policy is that if a precondition is violated, the results are undefined. However the locking functions may fail due to lack of system resources, and are allowed to throw an exception in such an event. The try-locking, unlocking, and non-blocking conversion functions are specified to not throw an exception. On these signatures the empty throw specification is intended to indicate that the function will not throw a signature, and is not intended to indicate that the function may call unexpected() (it may or may not do so if preconditions are violated leading to undefined behavior).

Default constructor

Not blocking

This constructs a mutex with default attributes (appropriate for the environment), and leaves the mutex in an unlocked state. The constructor may throw an exception to indicate failure.

Destructor

Not blocking

Preconditions: The mutex is in an unlocked state.

The mutex destructor does not throw any exceptions. It releases any resources that the mutex may have owned.

void lock()

Blocking

Precondition: This thread does not currently have sharable or upgradable ownership of the mutex. It can only have exclusive ownership if the mutex supports recursive locking.

Effects: Obtains exclusive ownership of the mutex, blocking if necessary. No other thread can have exclusive ownership, upgradable ownership, or sharable ownership while this thread owns exclusive ownership. The mutex may or may not support recursive locking on the same thread. If it does, the mutex must be unlocked the same number of times it is locked.

lock() may throw an exception on failure.

bool try_lock()

Not blocking

Precondition: This thread does not currently have sharable or upgradable ownership of the mutex. It can only have exclusive ownership if the mutex supports recursive locking.

Effects: Will attempt to obtain exclusive ownership of the mutex. The intent is that the call will return immediately if it is unable to obtain ownership. This ownership can not be shared with other thread's exclusive, upgradable or sharable ownership. The mutex may or may not support recursive locking on the same thread. If it does, the mutex must be unlocked the same number of times it is locked.

Returns: true if the lock was obtained, else returns false.

void unlock() throw()

Not blocking

Precondition: This thread currently owns exclusive ownership of the mutex. If the mutex is recursive, it must be unlocked the same number of times it was locked via the various routines which create exclusive ownership.

Effects: Will release exclusive ownership of the mutex. Some implementations may briefly block to gain permission to write to the mutex state. But in general this is a non-blocking operation.

Shared ownership

The shared ownership concept is a refinement of the exclusive ownership concept. In addition to implementing all of the requirements of the exclusive ownership concept, the mutex conforming to the shared ownership concept will also implement three additional signatures:

This concept introduces the ability for multiple threads to simultaneously own a mutex. The intent is that the threads holding shared ownership will not modify the protected data, but only read it. While any thread holds a sharable lock, no thread will hold an exclusive lock, and vice-versa.

void lock_sharable()

Blocking

Precondition: This thread does not currently have exclusive or upgradable ownership of the mutex. It can only have sharable ownership if the mutex supports recursive sharable locking.

Effects: Obtains sharable ownership of the mutex, blocking if necessary. No other thread can have exclusive ownership. But other threads may have upgradable ownership, or sharable ownership. The mutex may or may not support recursive sharable locking on the same thread. If it does, the mutex must be sharable-unlocked the same number of times it is sharable-locked.

lock_sharable() may throw an exception on failure.

bool try_lock_sharable()

Not blocking

Precondition: This thread does not currently have exclusive or upgradable ownership of the mutex. It can only have sharable ownership if the mutex supports recursive sharable locking.

Effects: Will attempt to obtain sharable ownership of the mutex. The intent is that the call will return immediately if it is unable to obtain ownership. This ownership can not be shared with other thread's exclusive ownership, but may be shared with other thread's upgradable or sharable ownership. The mutex may or may not support recursive sharable locking on the same thread. If it does, the mutex must be sharable-unlocked the same number of times it is sharable-locked.

Returns: true if the lock was obtained, else returns false.

void unlock_sharable() throw()

Not blocking

Precondition: This thread currently owns sharable ownership of the mutex. If the mutex is recursive, it must be sharable-unlocked the same number of times it was sharable-locked via the various routines which create sharable ownership.

Effects: Will release sharable ownership of the mutex. Some implementations may briefly block to gain permission to write to the mutex state. But in general this is a non-blocking operation.

Convertible shared ownership

The convertible shared ownership concept is a refinement of the shared ownership concept. In addition to implementing all of the requirements of the shared ownership concept, the mutex conforming to the convertible shared ownership concept will also implement two additional signatures:

This concept introduces the ability to convert an exclusive lock to a sharable lock (in a non-blocking manner), and to try to convert a sharable lock into an exclusive lock in a non-blocking manner, which is not always possible.

In converting an exclusive lock to a sharable lock, no other thread will be able to obtain exclusive ownership of the mutex during this transition. Thus whatever changes the thread made to the data under the exclusive lock are still valid under the sharable lock.

The conversion from a sharable lock to an exclusive lock is only guaranteed to succeed if no other thread is waiting for exclusive ownership, and if no other thread currently owns sharable or upgradable ownership of the mutex. Whether or not successful, the protected data which was read under the sharable lock will still be valid after the attempted conversion.

void unlock_and_lock_sharable() throw()

Not blocking

Precondition: This thread currently has exclusive ownership of the mutex. If the mutex is recursive, the exclusive lock count must be 1.

Effects: Will atomically release exclusive ownership of the mutex and obtain sharable ownership. After this operation, other threads waiting on sharable or upgradable ownership may also obtain ownership. Other threads waiting on exclusive ownership will not have a chance to gain exclusive ownership during this operation. Some implementations may briefly block to gain permission to write to the mutex state. But in general this is a non-blocking operation.

bool try_unlock_sharable_and_lock()

Not blocking

Precondition: This thread currently has sharable ownership of the mutex. If the mutex is recursive, the sharable lock count must be 1.

Effects: Will try to atomically release sharable ownership of the mutex and obtain exclusive ownership. If ownership is obtained it is guaranteed that no other thread will obtain exclusive ownership before this thread does. Therefore the thread may assume that what it has read under the sharable ownership is still valid (if exclusive ownership was obtained).

Returns: true if exclusive ownership was obtained, else returns false. If false is returned, the thread still has sharable ownership of the mutex.

Upgradable ownership

The upgradable ownership concept is a refinement of the convertible shared ownership concept. In addition to implementing all of the requirements of the convertible shared ownership concept, the mutex conforming to the upgradable ownership concept will also implement eight additional signatures:

This concept introduces the ability for a single thread which holds upgradable ownership to both share with other threads holding sharable ownership, while uniquely holding the privilege of upgrading this "shared ownership" to exclusive ownership without the danger of another thread gaining exclusive ownership during the transition (even though it is a potentially blocking transition). The protected data read under an upgradable lock will still be valid after the upgrade to an exclusive lock.

As the concept of upgradable is meaningless without also the concept of converting one type of lock to the other, the lock conversion functionality is included within this concept instead of having a separate convertible upgradable ownership concept.

void lock_upgradable()

Blocking

Precondition: This thread does not currently have exclusive or sharable ownership of the mutex. It can only have upgradable ownership if the mutex supports recursive upgradable locking.

Effects: Obtains upgradable ownership of the mutex, blocking if necessary. No other thread can have exclusive or upgradable ownership. But other threads may have sharable ownership. The mutex may or may not support recursive upgradable locking on the same thread. If it does, the mutex must be upgradable-unlocked the same number of times it is upgradable-locked.

lock_upgradable() may throw an exception on failure.

bool try_lock_upgradable()

Not blocking

Precondition: This thread does not currently have exclusive or sharable ownership of the mutex. It can only have upgradable ownership if the mutex supports recursive upgradable locking.

Effects: Will attempt to obtain upgradable ownership of the mutex. The intent is that the call will return immediately if it is unable to obtain ownership. This ownership can not be shared with other thread's exclusive ownership or upgradable ownership, but may be shared with other thread's sharable ownership. The mutex may or may not support recursive upgradable locking on the same thread. If it does, the mutex must be upgradable-unlocked the same number of times it is upgradable-locked.

Returns: true if the lock was obtained, else returns false.

void unlock_upgradable() throw()

Not blocking

Precondition: This thread currently has upgradable ownership of the mutex. If the mutex is recursive, it must be upgradable-unlocked the same number of times it was upgradable-locked via the various routines which create upgradable ownership.

Effects: Will release upgradable ownership of the mutex. Some implementations may briefly block to gain permission to write to the mutex state. But in general this is a non-blocking operation.

void unlock_upgradable_and_lock()

Blocking

Precondition: This thread currently has upgradable ownership of the mutex. If the mutex is recursive, the upgradable lock count must be 1.

Effects: Will atomically release upgradable ownership of the mutex and obtain exclusive ownership. During this operation it is possible that other threads will obtain sharable or upgradable ownership. However, it is guaranteed that no other thread will obtain exclusive ownership before this thread does. Therefore the thread may assume that what it has read under the upgradable ownership is still valid.

unlock_upgradable_and_lock() may throw an exception on failure.

bool try_unlock_upgradable_and_lock()

Not blocking

Precondition: This thread currently has upgradable ownership of the mutex. If the mutex is recursive, the upgradable lock count must be 1.

Effects: Will try to atomically release upgradable ownership of the mutex and obtain exclusive ownership. If ownership is obtained it is guaranteed that no other thread will obtain exclusive ownership before this thread does. Therefore the thread may assume that what it has read under the upgradable ownership is still valid (if exclusive ownership was obtained).

Returns: true if exclusive ownership was obtained, else returns false. If false is returned, the thread still has upgradable ownership of the mutex.

void unlock_and_lock_upgradable() throw()

Not blocking

Precondition: This thread currently has exclusive ownership of the mutex. If the mutex is recursive, the exclusive lock count must be 1.

Effects: Will atomically release exclusive ownership of the mutex and obtain upgradable ownership. After this operation, other threads waiting on sharable ownership may also obtain ownership. Other threads waiting on exclusive or upgradable ownership will not have a chance to gain ownership during this operation. Some implementations may briefly block to gain permission to write to the mutex state. But in general this is a non-blocking operation.

void unlock_upgradable_and_lock_sharable() throw()

Not blocking

Precondition: This thread currently has upgradable ownership of the mutex. If the mutex is recursive, the upgradable lock count must be 1.

Effects: Will atomically release upgradable ownership of the mutex and obtain sharable ownership. After this operation, other threads waiting on sharable or upgradable ownership may also obtain ownership. Other threads waiting on exclusive ownership will not have a chance to gain exclusive ownership during this operation. Some implementations may briefly block to gain permission to write to the mutex state. But in general this is a non-blocking operation.

bool try_unlock_sharable_and_lock_upgradable()

Not blocking

Precondition: This thread currently has sharable ownership of the mutex. If the mutex is recursive, the sharable lock count must be 1.

Effects: Will try to atomically release sharable ownership of the mutex and obtain upgradable ownership. If ownership is obtained it is guaranteed that no other thread will obtain exclusive ownership before this thread has an opportunity to upgrade to exclusive ownership. Therefore the thread may assume that what it has read under the sharable ownership is still valid (if exclusive ownership is eventually obtained).

Returns: true if upgradable ownership was obtained, else returns false. If false is returned, the thread still has sharable ownership of the mutex.

Lock Concepts

There are three different types of locks:

These three locks manage an appropriate mutex with a largely generic interface. The common part of this interface is described below for an imaginary type called basic_lock which essentially represents a lock concept. The interfaces unique to the three concrete types of locks are described later.

template <class Mutex>
class basic_lock
{
public:
    typedef Mutex mutex_type;
private:
    mutex_type* m_;  // exposition only
    bool owns_;      // exposition only
public:
    basic_lock();
    explicit basic_lock(mutex_type& m);
    basic_lock(mutex_type& m, detail::defer_lock_type);
    basic_lock(mutex_type& m, detail::try_lock_type);
    basic_lock(mutex_type& m, detail::accept_ownership_type);

    ~basic_lock();

    basic_lock(basic_lock&& bl);
    basic_lock& operator=(basic_lock&& bl);
private:
    basic_lock(const basic_lock&);
    basic_lock& operator=(const basic_lock&);
public:
    void lock();
    bool try_lock();
    void unlock();

    bool owns() const;
    operator unspecified-bool-type() const;
    mutex_type* mutex() const;
    
    void swap(basic_lock&& bl);

    mutex_type* release();
};

Each lock is templated on the mutex type, which is also represented by the nested type: mutex_type. These locks manage a pointer to a mutex (which may be null), and an owns flag. The lock does not govern the lifetime of the mutex. That is, the lock's responsibility is to lock / unlock the mutex in various ways, but not to own the memory which the mutex resides as a smart pointer would.

basic_lock();

Effects: The default constructor constructs a lock which owns no mutex, and refers to no mutex.

Throws: Nothing.

Postconditions:

mutex() == 0
owns() == false

basic_lock(mutex_type& m);

Effects: This explicit constructor constructs a lock which refers to the mutex m, and owns it. Calls lock() which will lock the mutex in a way defined differently for each type of lock, and may block in the process.

Preconditions: The lifetime of m is greater than the lifetime of this lock and of any object which ownership of this mutex may be transferred to.

Postconditions:

mutex() == &m
owns() == true

basic_lock(mutex_type& m, detail::defer_lock_type);

Effects: This explicit constructor constructs a lock which refers to the mutex m, but does not own it. It will not perform any operation on the mutex.

Preconditions: The lifetime of m is greater than the lifetime of this lock and of any object which ownership of this mutex may be transferred to.

Throws: Nothing.

Postconditions:

mutex() == &m
owns() == false

basic_lock(mutex_type& m, detail::try_lock_type);

Effects: This explicit constructor constructs a lock which refers to the mutex m. Calls try_lock() which will attempt to gain ownership of m in a non-blocking manner defined differently for each type of lock.

Preconditions: The lifetime of m is greater than the lifetime of this lock and of any object which ownership of this mutex may be transferred to.

Postconditions:

mutex() == &m
owns() will report the success or failure of the attempt to gain lock ownership.

basic_lock(mutex_type& m, detail::accept_ownership_type);

Effects: This explicit constructor constructs a lock which refers to the mutex m, and owns it, but will not perform any operation on the mutex.

Preconditions: This thread has appropriate ownership of this mutex, and no other object is managing that ownership. The lifetime of m is greater than the lifetime of this lock and of any object which ownership of this mutex may be transferred to.

Throws: Nothing.

Postconditions:

mutex() == &m
owns() == true

~basic_lock();

Effects: if owns() then calls unlock() which is defined appropriately for each lock.

Throws: Nothing.

basic_lock(basic_lock&& bl);

Effects: Mutex ownership (if any) is transferred from the rvalue source to this. There is no effect on the referenced mutex.

Throws: Nothing.

Postconditions:

mutex() == the value of bl.mutex() before the call.
owns() == the value of bl.owns() before the call.
bl.mutex() == 0
bl.owns() == false

basic_lock& operator=(basic_lock&& bl);

Effects: Mutex ownership (if any) is transferred from the rvalue source to this. There is no effect on the referenced source mutex. If this lock owns a mutex prior to the assignment, that mutex is unlocked with unlock().

Throws: Nothing.

Postconditions:

mutex() == the value of bl.mutex() before the call.
owns() == the value of bl.owns() before the call.
bl.mutex() == 0
bl.owns() == false

Notes: With a recursive mutex it is possible that both this and bl own the same mutex before the assignment. In this case, this will own the mutex after the assignment (and bl will not), but the mutex's lock count will be decremented by one.

basic_lock(const basic_lock&);

Private and left undefined. Lock types are not copyable.

basic_lock& operator=(const basic_lock& bl);

Private and left undefined. Lock types are not copyable.

void lock();

Effects: If mutex() == 0 or if owns() == true, throws a lock_error(). Otherwise the referenced mutex is locked in a manner defined appropriately for each lock (which may block execution of this thread).

Throws: lock_error. Exceptions may be thrown by the referenced mutex's locking function.

Postconditions: If an exception is thrown, there is no change of state to the lock. If an exception is not thrown, owns() == true.

void try_lock();

Effects: If mutex() == 0 or if owns() == true, throws a lock_error(). Otherwise the referenced mutex is locked in a manner defined appropriately for each lock, but in a non-blocking manner that may not succeed.

Throws: lock_error. Exceptions may be thrown by the referenced mutex's try-locking function.

Postconditions: If an exception is thrown, there is no change of state to the lock. If an exception is not thrown, owns() reflects the success or failure of the attempted locking function.

void unlock();

Effects: If mutex() == 0 or if owns() == false, throws a lock_error(). Otherwise the referenced mutex is unlocked in a manner defined appropriately for each lock.

Throws: lock_error.

Postconditions: owns() == false.

bool owns() const;

Effects: If mutex() == 0 returns false. Else returns true if this lock owns a lock on the mutex, else returns false.

Throws: Nothing.

operator unspecified-bool-type() const;

Effects: Returns non-null if owns() == true, else returns null.

Throws: Nothing.

mutex_type* mutex() const;

Effects: Returns a pointer to the referenced mutex, or 0 if there is no mutex to reference.

Throws: Nothing.

void swap(basic_lock&& bl);

Effects: Swaps state with bl.

Throws: Nothing.

Postconditions: The states of *this and bl are swapped.

mutex_type* release();

Effects: Returns a pointer to the referenced mutex, or 0 if there is no mutex to reference.

Throws: Nothing.

Postconditions:

mutex() == 0
owns() == false

scoped_lock<Mutex>

scoped_lock is meant to carry out the tasks for locking, unlocking and try-locking (recursive or not) for the Mutex. The Mutex must meet the mutex concept of exclusive ownership. Additionally, if the constructor accepting ownership from a sharable_lock is instantiated, the mutex must meet the convertible shared ownership requirements. If the constructor accepting ownership from an upgradable_lock is instantiated, the mutex must meet the upgradable ownership requirements.

Below the complete interface for scoped_lock is shown. However the description of each member will rely on the description of basic_lock as much as possible to prevent repetition. Lack of description for scoped_lock implies that the functionality is more completely described under basic_lock.

template <class Mutex>
class scoped_lock
{
public:
    typedef Mutex mutex_type;
private:
    mutex_type* m_;  // exposition only
    bool owns_;    // exposition only
public:
    scoped_lock();
    explicit scoped_lock(mutex_type& m);
    scoped_lock(mutex_type& m, detail::defer_lock_type);
    scoped_lock(mutex_type& m, detail::try_lock_type);
    scoped_lock(mutex_type& m, detail::accept_ownership_type);

    ~scoped_lock();

    scoped_lock(scoped_lock&& sl);
    explicit scoped_lock(upgradable_lock<mutex_type>&& r);
    scoped_lock(upgradable_lock<mutex_type>&& r, detail::try_lock_type);
    scoped_lock(sharable_lock<mutex_type>&& r, detail::try_lock_type);
    scoped_lock& operator=  (scoped_lock&& sl);
private:
    scoped_lock(scoped_lock const&);
    explicit scoped_lock(upgradable_lock<mutex_type> const&);
    scoped_lock(upgradable_lock<mutex_type> const&, detail::try_lock_type);
    scoped_lock(sharable_lock<mutex_type> const&, detail::try_lock_type);
    scoped_lock& operator=  (scoped_lock const&);
public:
    void lock();
    bool try_lock();
    void unlock();

    bool owns() const;
    operator unspecified-bool-type() const;
    const mutex_type* mutex() const;

    void swap(scoped_lock&& w);

    mutex_type* release();
};

template <class Mutex> void swap(scoped_lock<Mutex>& x, scoped_lock<Mutex>& y);
template <class Mutex> void swap(scoped_lock<Mutex>&& x, scoped_lock<Mutex>& y);
template <class Mutex> void swap(scoped_lock<Mutex>& x, scoped_lock<Mutex>&& y);

scoped_lock Constructors

scoped_lock(upgradable_lock<mutex_type>&& r);

Effects: If r.owns() returns true then calls unlock_upgradable_and_lock() on the referenced mutex. r.release() is called.

Postconditions:

mutex() == the value r.mutex() had before the construction.
owns() == the value r.owns() had before the construction.
r.mutex() == 0.
r.owns() == false.

Notes: If r is owned, this constructor will lock this scoped_lock while unlocking r. If r is unlocked, then this scoped_lock will be unlocked as well. Only rvalue upgradable_lock's will match this signature. lvalue scoped_lock's can not transfer ownership unless "cast" to an rvalue. An lvalue upgradable_lock can be "cast" to an rvalue with the expression: move(lock); This constructor may block if other threads hold a sharable_lock on this mutex (sharable_lock's can share ownership with an upgradable_lock).

[Example:

mutex m;
upgradable_lock<mutex> l1(mut);      // l1 upgradable-locked
scoped_lock<mutex> l2(move(l1)); // l2 locked, l1 unlocked

-- end example]

scoped_lock(upgradable_lock<mutex_type>&& r, detail::try_lock_type);

Effects: If r.owns() then calls try_unlock_upgradable_and_lock() on the referenced mutex.

Else r.owns() is false. mutex() obtains the value from r.release() and owns() is set to false

Notes: This construction will not block. It will try to obtain mutex ownership from r immediately, while changing the lock type from a "read lock" to a "write lock". If the "read lock" isn't held in the first place, the mutex merely changes type to an unlocked "write lock". If the "read lock" is held, then mutex transfer occurs only if it can do so in a non-blocking manner.

[Example:

mutex m;
upgradable_lock<mutex> l1(mut);      // l1 upgradable-locked
scoped_lock<mutex> l2(move(l1), try_to_lock); // l2 tries to obtain lock from l1

-- end example]

scoped_lock(sharable_lock<mutex_type>&& r, detail::try_lock_type);

Effects: If r.owns() then calls try_unlock_sharable_and_lock() on the referenced mutex.

Else r.owns() is false. mutex() obtains the value from r.release() and owns() is set to false

Notes: This construction will not block. It will try to obtain mutex ownership from r immediately, while changing the lock type from a "read lock" to a "write lock". If the "read lock" isn't held in the first place, the mutex merely changes type to an unlocked "write lock". If the "read lock" is held, then mutex transfer occurs only if it can do so in a non-blocking manner.

[Example:

mutex m;
sharable_lock<mutex> l1(mut);      // l1 sharable-locked
scoped_lock<mutex> l2(move(l1), try_to_lock); // l2 tries to obtain lock from l1

-- end example]

Locking functions

void lock();

Effects: Calls lock() on the referenced mutex.

bool try_lock();

Effects: Calls try_lock() on the referenced mutex.

void unlock();

Effects: Calls unlock() on the referenced mutex.

sharable_lock<Mutex>

sharable_lock is meant to carry out the tasks for sharable-locking (such as read-locking), unlocking and try-sharable-locking (recursive or not) for the Mutex. The Mutex must meet the mutex concept of shared ownership. Additionally, if the constructor or assignment operator accepting ownership from a scoped_lock is instantiated, the mutex must meet the convertible shared ownership requirements. If the constructor or assignment accepting ownership from an upgradable_lock is instantiated, the mutex must meet the upgradable ownership requirements.

Below the complete interface for sharable_lock is shown. However the description of each member will rely on the description of basic_lock as much as possible to prevent repetition. Lack of description for sharable_lock implies that the functionality is more completely described under basic_lock.

template <class RW_Mutex>
class sharable_lock
{
public:
    typedef RW_Mutex mutex_type;
private:
    mutex_type* m_;   // exposition only
    bool owns_;     // exposition only
public:
    sharable_lock();
    explicit sharable_lock(mutex_type& m);
    sharable_lock(mutex_type& m, detail::defer_lock_type);
    sharable_lock(mutex_type& m, detail::try_lock_type);

    ~sharable_lock();

    sharable_lock(sharable_lock&& r);
    explicit sharable_lock(upgradable_lock<mutex_type>&& r);
    explicit sharable_lock(scoped_lock<mutex_type>&& w);
    sharable_lock& operator=(sharable_lock&& r);
private:
    sharable_lock(sharable_lock const&);
    explicit sharable_lock(upgradable_lock<mutex_type> const&);
    explicit sharable_lock(scoped_lock<mutex_type> const&);
    sharable_lock& operator=(sharable_lock const&);
public:
    void lock();
    bool try_lock();
    void unlock();

    bool owns() const;
    operator unspecified-bool-type() const;
    const mutex_type* mutex() const;

    void swap(sharable_lock&& r);

    mutex_type* release();
};

template <typename Mutex> void swap(sharable_lock<Mutex>& x, sharable_lock<Mutex>& y);

sharable_lock Constructors

sharable_lock(upgradable_lock<mutex_type>&& r);

Effects: m_->unlock_upgradable_and_lock_sharable() if r.owns().

Postconditions:

mutex() == the value r.mutex() had before the construction.
owns() == the value r.owns() had before the construction.
r.mutex() == 0.
r.owns() == false.

Notes: If r is owned, this constructor will lock this sharable_lock while unlocking r.

[Example:

mutex m;
upgradable_lock<mutex> l1(mut);  // l1 upgradable-locked
sharable_lock<mutex> l2(move(l1));   // l2 sharable-locked, l1
unlocked

-- end example]

sharable_lock(scoped_lock<mutex_type>&& w);

Effects: If w.owns(), m_->unlock_and_lock_sharable().

Postconditions:

mutex() == the value w.mutex() had before the construction.
owns() == the value w.owns() had before the construction.
w.mutex() == 0.
w.owns() == false.

Notes: If w is owned, this constructor will transfer the exclusive ownership to a sharable-ownership of this sharable_lock. Only rvalue scoped_lock's will match this signature. lvalue scoped_lock's can not transfer ownership unless "cast" to an rvalue. An lvalue scoped_lock can be "cast" to an rvalue with the expression: move(lock);

[Example:

mutex m;
scoped_lock<mutex> wl(mut);     // wl locked
sharable_lock<mutex> rl(move(wl)); // rl sharable-locked, wl unlocked

-- end example]

Locking functions

void lock();

Effects: Calls lock_sharable() on the referenced mutex.

bool try_lock();

Effects: Calls try_lock_sharable() on the referenced mutex.

void unlock();

Effects: Calls unlock_sharable() on the referenced mutex.

upgradable_lock<Mutex>

upgradable_lock is meant to carry out the tasks for read-locking, unlocking and try-read-locking (recursive or not) for the mutex. Additionally the upgradable_lock can transfer ownership to a scoped_lock with move syntax. The mutex must meet the mutex concept of upgradable ownership.

Below the complete interface for upgradable_lock is shown. However the description of each member will rely on the description of basic_lock as much as possible to prevent repetition. Lack of description for upgradable_lock implies that the functionality is more completely described under basic_lock.

template <class RW_Mutex>
class upgradable_lock
{
public:
    typedef RW_Mutex mutex_type;
private:
    mutex_type* m_;   // exposition only
    bool owns_;     // exposition only
public:
    upgradable_lock();
    explicit upgradable_lock(mutex_type& m);
    upgradable_lock(mutex_type& m, detail::defer_lock_type);
    upgradable_lock(mutex_type& m, detail::try_lock_type);

    ~upgradable_lock();

    upgradable_lock(upgradable_lock&& r);
    explicit upgradable_lock(scoped_lock<mutex_type>&& w);
    upgradable_lock(sharable_lock<mutex_type>&& r, detail::try_lock_type);
    upgradable_lock& operator=(upgradable_lock&& r);
private:
    upgradable_lock(upgradable_lock const&);
    explicit upgradable_lock(scoped_lock<mutex_type> const&);
    upgradable_lock(sharable_lock<mutex_type> const& r, detail::try_lock_type);
    upgradable_lock& operator=(upgradable_lock const&);
public:
    void lock();
    bool try_lock();
    void unlock();

    bool owns() const;
    operator unspecified-bool-type() const;
    const mutex_type* mutex() const;

    void swap(upgradable_lock&& r);

    mutex_type* release();
};

template <typename Mutex> void swap(upgradable_lock<Mutex>& x, upgradable_lock<Mutex>& y);

upgradable_lock Constructors

upgradable_lock(scoped_lock<mutex_type>&& w);

Effects: If w.owns(), m_.unlock_and_lock_upgradable().

Postconditions:

mutex() == the value w.mutex() had before the construction.
owns() == the value w.owns() had before the construction.
w.mutex() == 0.
w.owns() == false.

Notes: If w is owned, this constructor will transfer the exclusive-ownership to a upgradable-ownership of this upgradable_lock. Only rvalue scoped_lock's will match this signature. lvalue scoped_lock's can not transfer ownership unless "cast" to an rvalue. An lvalue scoped_lock can be "cast" to an rvalue with the expression: move(lock);

[Example:

mutex m;
scoped_lock<mutex> wl(mut);     // wl locked
upgradable_lock<mutex> rl(move(wl)); // rl upgradable-locked, wl unlocked

-- end example]

upgradable_lock(sharable_lock<mutex_type>&& r, detail::try_lock_type);

Effects: If r.owns() then calls try_unlock_sharable_and_lock_upgradable() on the referenced mutex.

Else r.owns() is false. mutex() obtains the value from r.release() and owns() is set to false

Postconditions:

mutex() == the value r.mutex() had before the construction.
owns() == the value r.owns() had before the construction.
r.mutex() == 0.
r.owns() == false.

Notes: This construction will not block. It will try to obtain mutex ownership from r immediately, while changing the lock type from a "read lock" to an "upgradable lock". If the "read lock" isn't held in the first place, the mutex merely changes type to an unlocked "upgradable lock". If the "read lock" is held, then mutex transfer occurs only if it can do so in a non-blocking manner.

[Example:

mutex m;
sharable_lock<mutex> l1(mut);      // l1 sharable-locked
upgradable_lock<mutex> l2(move(l1), try_to_lock); // l2 tries to obtain lock from l1

-- end example]

Locking functions

void lock();

Effects: Calls lock_upgradable() on the referenced mutex.

bool try_lock();

Effects: Calls try_lock_upgradable() on the referenced mutex.

void unlock();

Effects: Calls unlock_upgradable() on the referenced mutex.

Conversion Summary

The unlabeled arrows in the figure indicate non-blocking conversions. These non-blocking conversions are available with the move constructor.

The try-labeled arrows in the figure indicate try-conversions. These are available with constructors taking try_to_lock as the second parameter.

There is one blocking conversion from upgradable_lock to scoped_lock. This is available with move construction syntax. Indeed it is this blocking conversion that is upgradable_lock's reason for existing. A thread holding an upgradable_lock (shared ownership) can convert that ownership to a scoped_lock (exclusive ownership) without the danger that another thread will obtain exclusive ownership during the conversion.