Howard Hinnant
2010-12-13

Handling mutexes in C++

Contents

Introduction

This is a tutorial on locking mutexes in C++. It also describes a non-standard library which introduces "shared" or "read/write" locking. This library works with the standard mutexes and locks, and indeed was originally designed at the same time as the standard mutexes and locks.

The shared_mutex library described herein very closely resembles that described in N2406 and the boost thread library. Where differences exist, this document will both highlight and motivate the difference.

Source code for this library can be found in shared_mutex and shared_mutex.cpp.

This library adds a few constructors to unique_lock. libc++ is set up to handle this with #define _LIBCPP_SHARED_LOCK.

Exclusive Locking

Most of the time one needs to lock a mutex, the tools are:

These can be used to assure exclusive access to a block of code. For example:

#include <iostream>
#include <mutex>

void sub_print() {}

template <class A0, class ...Args>
void
sub_print(const A0& a0, const Args& ...args)
{
    std::cout << a0;
    sub_print(args...);
}

std::mutex&
cout_mut()
{
    static std::mutex m;
    return m;
}

template <class ...Args>
void
print(const Args& ...args)
{
    std::lock_guard<std::mutex> _(cout_mut());
    sub_print(args...);
}

The above is a simple but very useful utility to atomically stream multiple arbitrary objects to std::cout. The parts pertaining to mutex locking are highlighted. All that is happening is the mutex is locked when print is entered, and is unlocked when print is exited. The unlocking happens whether print is exited normally or exceptionally.

This latter point deserves emphasis, as this design philosophy permeates the design of both the std::library and the non-standard shared mutex library. Consider this rewrite of print:

template <class ...Args>
void
print(const Args& ...args)
{
    cout_mut().lock();  // bad
    sub_print(args...);
    cout_mut().unlock();  // bad
}

Under normal circumstances, there is no difference between this rewrite and the original. However when sub_print throws an exception, this rewrite incorrectly leaves cout_mut() locked as the exception propagates out of print. And once this happens, the entire program is corrupted. For example any caller that now calls print, from any thread, is now likely to deadlock (bring the application, or at least that thread, to a halt).

The lock ownership of a mutex must be considered a resource, just like allocated memory. The lock ownership must not be leaked, even under exceptional circumstances. Failure to properly manage this resource will result in a poorly behaved, buggy application. This is precisely the reason tools such as std::lock_guard<std::mutex> exist. If you use these "locks", instead of calling the lock()/unlock() member functions of the mutex yourself, then you are far more likely to write code that is robust in the event of exceptions.

Question: One might wonder: If calling the member functions of a mutex is so bad, why are they exposed?

The original boost mutexes/locks did not expose these member functions. Thus clients could not fall into the trap of using them. However it was soon realized that more advanced clients of mutexes exist where calling the member functions is actually the correct thing to do. It is cumbersome, and ultimately impossible to grant friendship to all of these advanced clients. Thus education is really the best tool: For casual use, do not call the member functions of a mutex directly. Use locks such as std::lock_guard<std::mutex> instead.

Why is lock_guard templated?

The reason lock_guard is templated is because there is more than one type of mutex upon which you might want to simply lock and unlock over a scope as lock_guard does. For example consider this rewrite of print:

std::recursive_mutex&
cout_mut()
{
    static std::recursive_mutex m;
    return m;
}

void print() {}

template <class A0, class ...Args>
void
print(const A0& a0, const Args& ...args)
{
    std::lock_guard<std::recursive_mutex> _(cout_mut());
    std::cout << a0;
    print(args...);
}

In this correct rewrite, recursive_mutex is used instead of mutex. print calls itself recursively, instead of calling sub_print and locks cout_mut() on each call. This design is correct as long as cout_mut() provides a mutex which can be locked recursively.

Question: Is recursive_mutex the only reason lock_guard is templated?

No. There are actually many types of mutexes which you might want to use with lock_guard. recursive_mutex is just a simple example of another type of mutex. Unlike other systems such as POSIX, C++ makes use of the type system as much as possible with mutexes. A non-recursive mutex which is capable of only lock() and unlock() can be a very efficient locking mechanism. As soon as you start adding more features to it, such as recursion, timed locking, or shared locking, it becomes more expensive. In C++ we add this expense to different types of mutexes so that you can choose which type of mutex you need. That way you only pay for the feature if you use it.

Question: Can I try_lock(), try_lock_for(), or try_lock_until() with a lock_guard?

No. An invariant of lock_guard is that it always owns the lock of its referenced mutex, and it always references a mutex. This is both a feature and a bug. It is a feature for those times when you have a lot of code that you need unconditionally protected. Use lock_guard to state in your code that the scope is unconditionally guarded by the mutex. If you need more flexibility in your locking pattern, the solution is usually to use unique_lock (or some other lock) rather than lock_guard, or rather than to call the member functions of the mutex directly.

unique_lock tutorial

std::unique_lock<std::mutex> is the tool of choice when your locking needs are more complex than a simple lock at the beginning followed unconditionally by an unlock at the end. For example you may need to construct a lock but tell the lock that it should not lock the referenced mutex upon construction, because you need to lock it later. This can be done like so:

std::mutex mut;
std::unique_lock<std::mutex> lk(mut, std::defer_lock);
// lk.owns_lock() == false

This is not so far fetched. For example consider the following incorrect implementation of A::operator=(const A&):

class A
{
    mutable std::mutex  mut_;
    std::vector<double> data_;

public:
    // ...

    A& operator=(const A& rhs)
    {
        if (this != &rhs)
        {
            std::unique_lock<std::mutex> lhs_lock(mut_);
            std::unique_lock<std::mutex> rhs_lock(rhs.mut_);  // deadlock!
            // assign data ...
            data_ = rhs.data_;
        }
        return *this;
    }

    // ...
};

The reason this is wrong is because one thread may assign a1 = a2 at the same time that another thread assigns a2 = a1. Doing so locks a1.mut_ and a2.mut_ in opposite orders. Locking two mutexes in opposite order is a recipe for deadlock. Thread A will lock a1.mut_ while Thread B locks a2.mut_. Then thread A will block on locking a2.mut_ while thread B blocks on locking a1.mut_. Neither thread can advance and neither thread will relinquish its lock so that the other thread may advance.

Fortunately there is an easy standard solution for this problem:

class A
{
    mutable std::mutex  mut_;
    std::vector<double> data_;

public:
    // ...

    A& operator=(const A& rhs)
    {
        if (this != &rhs)
        {
            std::unique_lock<std::mutex> lhs_lock(    mut_, std::defer_lock);
            std::unique_lock<std::mutex> rhs_lock(rhs.mut_, std::defer_lock);
            std::lock(lhs_lock, rhs_lock);
            // assign data ...
            data_ = rhs.data_;
        }
        return *this;
    }

    // ...
};

Explanation:

The biggest difference between lock_guard and unique_lock is that lock_guard always owns the lock mode of the referenced mutex and unique_lock doesn't. Indeed, unique_lock may not even reference a mutex. This makes unique_lock much more powerful and much more complicated (and slightly more expensive) than lock_guard. The interface of unique_lock is strict superset of the interface of lock_guard. If you were to take unique_lock and remove every constructor and member function that allows the lock to be left in a state such that it does not reference a mutex which owns the locking state of said mutex, you would be left with the interface of lock_guard. For example:

This extra power (and expense) means that unique_lock can be put into containers, and returned from factory functions. It can be used with condition_variable and condition_variable_any. Later in this paper shared_mutex will be introduced. In its implementation, lock_guard is used in several places. However in several more places in the implementation lock_guard lacks the required functionality and unique_lock is required. In this example the same std::mutex is used, but in some places with lock_guard and in others with unique_lock. It is all about choosing the appropriate tool for each use. Pick the simplest, cheapest tool possible for each use. This increases both code efficiency and code readability. If you're not sure which tool to use, start with lock_guard. If your code does not compile, then that probably means that lock_guard doesn't have the power you need. Otherwise it does, and reviewers of your code will more quickly understand the limits of the mutex manipulation within your code.

Shared Locking

Shared-locking should be nothing but a performance optimization. If you change a lock from exclusive to shared, it will not make it more correct (modulo very tricky situations most of us don't want to think about). Indeed it could make it less correct! But it may make it faster by permitting greater concurrency on a multi-core architecture. See here for examples. If you change a lock from exclusive to shared and it makes your application slower or buggy, then change it back to exclusive.

In general one hears that one should hold a lock for as little time as possible. This is good advice. When you're holding an exclusive lock, you are blocking concurrancy for that piece of code. This means that you may not be getting your money's worth out of your multi-core computer. However, taking this view to its extreme can lead to incorrect code: sometimes locking is necessary. And when one finds that there is a significant amount of code that needs protecting, then one should generally look for ways to give your locking a finer granularity in order to gain back some concurrency.

One way to divide up your locking is to recognize that some tasks do not modify the data under their lock. They require only a self-consistent, and non-changing state of the data while they have it locked. These tasks are often called readers, and since they are not modifying the protected data, they can share their lock with other readers, though not with writers. This is the domain of shared_mutex.

A shared_mutex can be more expensive to lock and unlock. However when you have multiple reader threads that would otherwise require an exclusive lock for "long" periods of time, then shared locking is an important technique for regaining some concurrency through a protected section of code.

Consider the case where we're writing the thread-safe assignment operator of A and the "assigning the data" part is very expensive.

class A
{
public:
    // ...

    A& operator=(const A& rhs)
    {
        if (this != &rhs)
        {
            // assign data ...
            // expensive code here ...
        }
        return *this;
    }

    // ...
};

It might make sense to "share lock" rhs while doing a normal exclusive lock on *this. A "share lock" (also known as a "read lock") is a promise that under this lock you are only going to look at the data. You are not going to modify it. Thus you can share this lock with all other threads that make the same promise. But not with any threads which will modify the data. Thus while you hold the "share lock", you are guaranteed a consistent state, and you allow higher concurrency providing other threads needing the same service.

With shared_mutex this can be accomplished like so:

class A
{
    typedef ting::shared_mutex            mutex_type;
    typedef ting::shared_lock<mutex_type> SharedLock;
    typedef std::unique_lock<mutex_type>  ExclusiveLock;

    mutable mutex_type  mut_;
    std::vector<double> data_;

public:
    // ...

    A& operator=(const A& rhs)
    {
        if (this != &rhs)
        {
            ExclusiveLock lhs_lock(mut_,   std::defer_lock);
            SharedLock    rhs_lock(rhs.mut_, std::defer_lock);
            std::lock(lhs_lock, rhs_lock);
            data_ = rhs.data_;
        }
        return *this;
    }

    // ...

Now A holds a ting::shared_mutex instead of a std::mutex. The ting::shared_mutex has all of the functionality std::mutex has (and indeed all of the functionality std::timed_mutex has) and adds:

These are all variations on existing std::timed_mutex member functions but with "shared" semantics. The shared variations lock the mutex in "shared mode" as opposed to "exclusive mode". This means that other threads can also concurrently lock the mutex in "shared mode". But no thread can lock the mutex in "exclusive mode" while any thread holds a "shared mode" lock.

A shared_lock will lock its referenced mutex in shared mode instead of in exclusive mode. To lock a shared_mutex in exclusive mode, you should use std::unique_lock<ting::shared_mutex>. For example review the above A::operator=:

shared_lock can also come in handy in implementing A's copy constructor:

A(const A& rhs)
{
    SharedLock _(rhs.mut_);
    data_ = rhs.data_;
}

This allows several threads to concurrently construct a new A from the same source. The diagram to the right illustrates the exclusive and shared locking modes of ting::shared_mutex. The upward pointing arrows indicate either a blocking (labeled with B) operation, or a timed or try-operation (labeled with T). The downward pointing arrows indicate a non-blocking "unlock" operation.

Question: I've heard about writer and reader starvation. Do I need to worry about that?

This implementation of shared_mutex avoids starvation by using a two-gate algorithm developed by Alexander Terekhov. The OS decides which thread is the next to enter the first gate. Once reader threads enter the first gate, they have gained a shared lock. Once a writer thread enters the first gate it blocks on the second gate. Blocking on the second gate inhibits all other threads (reader or writer) from entering the first gate. The writer thread is unblocked from the second gate (and obtains the exclusive lock) when there are no more readers beyond the first gate. This algorithm is as fair as is the OS's algorithm at giving threads the exclusive lock of a mutex.

Question: If I have a shared_mutex locked in shared or exclusive mode, can I change its mode to the other state (exclusive or shared) without first unlocking it?

No. This is both a feature and a bug. There is yet another mutex type (ting::upgrade_mutex) which does allow such conversions. If such conversions make you nervous and you want to ensure that you don't accidentally do them in your code, then stick with ting::shared_mutex: it can not change ownership modes on you. However if changing ownership modes is what you need, then you should look at ting::upgrade_mutex.

Note: If you are using boost::shared_mutex it can change ownership modes. And there is no separate boost::upgrade_mutex type. I consider this a defect in the boost library. Being able to statically confirm that your mutex can not change ownership modes is an important feature present herein and lacking in the boost library.

Upgrade Locking

ting::upgrade_mutex and ting::upgrade_lock<mutex_type> add the ability to change the ownership mode of a mutex from shared to exclusive and back. To accomplish this without deadlock a third ownership mode must be added: upgrade_lock.

The reason a third ownership mode must be added is because you can not promise more than one thread with shared ownership to be able to upgrade to exclusive ownership mode. You can promise exactly one thread with shared ownership upgrade privileges. When that thread upgrades, all other threads with shared ownership must have given up their shared ownership. Only then can a thread convert from shared ownership to exclusive ownership. If two threads are waiting to upgrade, then there is a deadlock because they are each waiting on the other to relinquish their shared ownership before exclusive ownership is possible.

To ensure that only one thread can upgrade from shared ownership to exclusive ownership, upgrade ownership mode is introduced. With upgrade ownership mode a thread shares ownership with all threads requesting shared ownership mode. However it does not share ownership with other threads requesting upgrade or exclusive ownership. Thus when it comes time for the thread to convert to exclusive ownership, all it must do is block until there are no other threads with shared ownership present, and then it can safely convert (with no deadlock).

ting::upgrade_mutex adds lock_upgrade, unlock_upgrade, unlock_upgrade_and_lock, and unlock_and_lock_upgrade as illustrated in the figure. The upward pointing arrows represent blocking operations, and the downward pointing arrows represent non-blocking operations.

This can be useful if, for example, part of your function requires read-only access to some data while another part requires write-access, and must assume that the read-only part hasn't changed when it gets to the write-access part. The opposite can also be true: You've changed some data under an exclusive lock and now you need to just access it but no longer modify it. You might as well give other threads shared access while you're not changing it. The following example member function which takes two A's and averages them, replacing both with the average, illustrates this point:

class A
{
    typedef ting::upgrade_mutex            mutex_type;
    typedef ting::shared_lock<mutex_type>  SharedLock;
    typedef ting::upgrade_lock<mutex_type> UpgradeLock;
    typedef std::unique_lock<mutex_type>   ExclusiveLock;

    mutable mutex_type  mut_;
    std::vector<double> data_;

public:

    void average(A& a)
    {
        assert(data_.size() == a.data_.size());
        assert(this != &a);

        ExclusiveLock lhs_lock      (  mut_, std::defer_lock);
        UpgradeLock   share_rhs_lock(a.mut_, std::defer_lock);
        std::lock(lhs_lock, share_rhs_lock);
        // *this is exclusive-locked and a is upgrade-locked
        for (unsigned i = 0; i < data_.size(); ++i)
            data_[i] = (data_[i] + a.data_[i]) / 2;
        SharedLock    share_lhs_lock(std::move(lhs_lock));
        ExclusiveLock rhs_lock(std::move(share_rhs_lock));
        // *this is shared-locked and a is exclusive-locked
        a.data_ = data_;
    }
};

Note how ubiquitous move semantics syntax is used with locks to convert the underlying ownership mode of the mutexes. This is far easier than memorizing and correctly using (in an exception safe manner) the many member functions of ting::upgrade_mutex. The above example is calling (under the covers) the following member functions of ting::upgrade_mutex:

Some of these member functions are only called in an exceptional code path. In any event, whether the function exits normally or exceptionally from any point, both mutexes are correctly unlocked. This would be impractical to do (and get it right) without using the lock objects.

Additionally it is a fundamental design decision of this library that any time there exists a blocking operation, that there also exist timed and try versions of that operation. Lack of such functionality can severely curtail a real-time application needing to meet firm scheduling deadlines. This is illustrated in the diagram to the right with the arrows in purple and marked with "T". The boost version of this library currently lacks this functionality, but that is reported to be a mere oversight.

These operations can easily be used with upgrade_lock and unique_lock using the regular syntax for try and timed construction of locks. There is no need for the client to use the necessarily ugly upgrade_mutex member function syntax. And doing so would probably introduce exception safety bugs.

An example is shown below which uses the "try_until" functionality using locks. It is a variation of the average function shown earlier that assumes that the numerics won't take anywhere near 1/2 second, and if the blocking locks are taking that much time, then it should return false, aborting the operation.

// Introduce try_lock_until utility
template <class Clock, class Duration, class L0, class L1>
int
try_lock_until(const std::chrono::time_point<Clock, Duration>& t,
               L0& l0, L1& l1)
{
    std::unique_lock<L0> u0(l0, t);
    if (u0.owns_lock())
    {
        if (l1.try_lock_until(t))
        {
            u0.release();
            return -1;
        }
        else
            return 1;
    }
    return 0;
}

template <class Clock, class Duration, class L0, class L1, class L2,
                                                                   class... L3>
int
try_lock_until(const std::chrono::time_point<Clock, Duration>& t,
               L0& l0, L1& l1, L2& l2, L3&... l3)
{
    int r = 0;
    std::unique_lock<L0> u0(l0, t);
    if (u0.owns_lock())
    {
        r = try_lock_until(t, l1, l2, l3...);
        if (r == -1)
            u0.release();
        else
            ++r;
    }
    return r;
}

// This function performs its computation only if it can be accomplished
// without "excessive" blocking.  It returns true if the computation was
// done and false if it timed out.
bool
average(A& a)
{
    assert(data_.size() == a.data_.size());
    assert(this != &a);

    std::chrono::milliseconds time_limit(500);
    ExclusiveLock this_lock(mut_, std::defer_lock);
    UpgradeLock   share_that_lock(a.mut_, std::defer_lock);
    if (try_lock_until(time_limit, this_lock, share_that_lock) != -1)
        return false;
    for (unsigned i = 0; i < data_.size(); ++i)
        data_[i] = (data_[i] + a.data_[i]) / 2;
    SharedLock    share_this_lock(std::move(this_lock));
    ExclusiveLock that_lock(std::move(share_that_lock), time_limit);
    if (!that_lock.owns_lock())
        return false;
    a.data_ = data_;
    return true;
}

Question: What if I don't know until run time which thread I want to upgrade from shared ownership to exclusive ownership?

One can pick any shared ownership mode and try to convert it to exclusive ownership. The conversion is not guaranteed to succeed. But if no other threads currently hold shared or upgrade ownership, then it will succeed. If you expect low contention for this operation, and if you have logic that will deal with a failed attempt (something better than immediately trying again), then you can do this with the following syntax:

bool time_to_update = true;

typedef ting::upgrade_mutex Mutex;
Mutex mut;

void read();   // takes a long time
void update(); // takes a short time
void export(); // takes a short time

void updater_task()
{
    if (time_to_update)
    {
        ting::upgrade_lock<Mutex> ul(mut);
        // Must read() first to make consistent update()
        read();
        std::unique_lock<Mutex> lk(std::move(ul));
        update();
        time_to_update = false;
    }
}

void reader_task()
{
    ting::shared_lock<Mutex> sl(mut);
    read();
    export();
    if (time_to_update)
    {
        std::unique_lock<Mutex> lk(std::move(sl), std::try_to_lock);
        if (lk.owns_lock())
        {
            // update() is consistent since we've already read()
            update();
            time_to_update = false;
        }
        // else updater_task() will get to it, just be less efficient
    }
}

In this example the reader_task can, as a pure optimization, also perform the update task. It can only perform the optimization if the conversion from shared locking mode to exclusive locking mode is successful. If there is sufficient contention that the conversion is unsuccessful, then the job gets done under updater_task. This is more expensive because updater_task has to repeat the expensive read() operation only for the purpose of making a consistent update().

These try (and timed) ownership conversions are represented in red arrows in the figure to the right, pointing from Shared to Exclusive and Upgrade. The arrows are in red to indicate that the boost version of this library does not currently include these conversions. Boost does however include the non-blocking reverse conversions from Exclusive and Upgrade to Shared.

The chart now illustrates the full compliment of ownership mode conversion afforded by upgrade_mutex and most easily used using move semantics syntax among the three locks: unique_lock<upgrade_mutex>, upgrade_lock<upgrade_mutex> and shared_lock<upgrade_mutex>.

The functionality of upgrade_mutex can be daunting. However sometimes it is functionality needed by real-world applications. Recall that most of the time, lock_guard<mutex> is all you need. You should only use these more complicated mutexes and locks when your application demands it. And if you make a mistake (e.g. try to convert ownership modes using a shared_muetx, or attempt an unconditional conversion where none exists - because it might deadlock, then a compile-time error will result, not a run-time error).

Reference Synopses

Each constructor or member function which could block indefinitely is commented with // blocking. Those constructors and member functions which take a duration or time_point can block for the indicated amount of time. All other members do not block.

mutex

Header: <mutex>

namespace std
{

class mutex
{
public:
     mutex();
     ~mutex();

    mutex(const mutex&) = delete;
    mutex& operator=(const mutex&) = delete;

    // Exclusive ownership

    void lock();  // blocking
    bool try_lock();
    void unlock();

    typedef implementation-defined native_handle_type;
    native_handle_type native_handle();
};

}  // std

recursive_mutex

Header: <mutex>

namespace std
{

class recursive_mutex
{
public:
     recursive_mutex();
     ~recursive_mutex();

    recursive_mutex(const recursive_mutex&) = delete;
    recursive_mutex& operator=(const recursive_mutex&) = delete;

    // Exclusive ownership

    void lock();  // blocking
    bool try_lock();
    void unlock();

    typedef implementation-defined native_handle_type;
    native_handle_type native_handle();
};

}  // std

timed_mutex

Header: <mutex>

namespace std
{

class timed_mutex
{
public:
     timed_mutex();
     ~timed_mutex();

    timed_mutex(const timed_mutex&) = delete;
    timed_mutex& operator=(const timed_mutex&) = delete;

    // Exclusive ownership

    void lock();  // blocking
    bool try_lock();
    void unlock();

    // Exclusive timed locking ownership

    template <class Rep, class Period>
        bool try_lock_for(const chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
        bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);

    typedef implementation-defined native_handle_type;
    native_handle_type native_handle();
};

}  // std

recursive_timed_mutex

Header: <mutex>

namespace std
{

class recursive_timed_mutex
{
public:
     recursive_timed_mutex();
     ~recursive_timed_mutex();

    recursive_timed_mutex(const recursive_timed_mutex&) = delete;
    recursive_timed_mutex& operator=(const recursive_timed_mutex&) = delete;

    // Exclusive ownership

    void lock();  // blocking
    bool try_lock();
    void unlock();

    // Exclusive timed locking ownership

    template <class Rep, class Period>
        bool try_lock_for(const chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
        bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);

    typedef implementation-defined native_handle_type;
    native_handle_type native_handle();
};

}  // std

lock_guard

Header: <mutex>

namespace std
{

struct defer_lock_t {};
struct try_to_lock_t {};
struct adopt_lock_t {};

constexpr defer_lock_t  defer_lock{};
constexpr try_to_lock_t try_to_lock{};
constexpr adopt_lock_t  adopt_lock{};

template <class Mutex>
class lock_guard
{
public:
    typedef Mutex mutex_type;

    explicit lock_guard(mutex_type& m);  // blocking
    lock_guard(mutex_type& m, adopt_lock_t);
    ~lock_guard();

    lock_guard(lock_guard const&) = delete;
    lock_guard& operator=(lock_guard const&) = delete;
};

}  // std

unique_lock

Header: <mutex>

namespace std
{

template <class Mutex>
class unique_lock
{
public:
    typedef Mutex mutex_type;

    unique_lock();

    // Exclusive locking

    explicit unique_lock(mutex_type& m);  // blocking
    unique_lock(mutex_type& m, defer_lock_t);
    unique_lock(mutex_type& m, try_to_lock_t);
    unique_lock(mutex_type& m, adopt_lock_t);
    template <class Clock, class Duration>
        unique_lock(mutex_type& m, const chrono::time_point<Clock, Duration>& abs_time);
    template <class Rep, class Period>
        unique_lock(mutex_type& m, const chrono::duration<Rep, Period>& rel_time);
    ~unique_lock();

    unique_lock(unique_lock const&) = delete;
    unique_lock& operator=(unique_lock const&) = delete;

    unique_lock(unique_lock&& u);
    unique_lock& operator=(unique_lock&& u);

    void lock();  // blocking
    bool try_lock();
    template <class Rep, class Period>
        bool try_lock_for(const chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
        bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);
    void unlock();

    // Conversion from shared locking (non-standard)

    unique_lock(ting::shared_lock<mutex_type>&& sl, try_to_lock_type);
    template <class Clock, class Duration>
        unique_lock(ting::shared_lock<mutex_type>&& sl,
                    const chrono::time_point<Clock, Duration>& abs_time);
    template <class Rep, class Period>
        unique_lock(ting::shared_lock<mutex_type>&& sl,
                    const chrono::duration<Rep, Period>& rel_time);

    // Conversion from upgrade locking (non-standard)

    explicit unique_lock(ting::upgrade_lock<mutex_type>&& ul);  // blocking
    unique_lock(ting::upgrade_lock<mutex_type>&& ul, try_to_lock_type);
    template <class Clock, class Duration>
        unique_lock(ting::upgrade_lock<mutex_type>&& ul,
                    const chrono::time_point<Clock, Duration>& abs_time);
    template <class Rep, class Period>
        unique_lock(ting::upgrade_lock<mutex_type>&& ul,
                    const chrono::duration<Rep, Period>& rel_time);

    // Setters

    void swap(unique_lock& u);
    mutex_type* release();

    // Getters

    bool owns_lock() const;
    explicit operator bool () const;
    mutex_type* mutex() const;
};

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

}  // std

Multi-lock locking

Header: <mutex>

namespace std
{

template <class L1, class L2, class... L3>
    int try_lock(L1&, L2&, L3&...);
template <class L1, class L2, class... L3>
    void lock(L1&, L2&, L3&...);  // blocking

}  // std

shared_mutex

Header: <shared_mutex>

namespace ting
{

class shared_mutex
{
public:

    shared_mutex();
    ~shared_mutex();

    shared_mutex(const shared_mutex&) = delete;
    shared_mutex& operator=(const shared_mutex&) = delete;

    // Exclusive ownership

    void lock();  // blocking
    bool try_lock();
    template <class Rep, class Period>
        bool try_lock_for(const std::chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
        bool
        try_lock_until(
                      const std::chrono::time_point<Clock, Duration>& abs_time);
    void unlock();

    // Shared ownership

    void lock_shared();  // blocking
    bool try_lock_shared();
    template <class Rep, class Period>
        bool
        try_lock_shared_for(const std::chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
        bool
        try_lock_shared_until(
                      const std::chrono::time_point<Clock, Duration>& abs_time);
    void unlock_shared();
};

}  // ting

upgrade_mutex

Header: <shared_mutex>

namespace ting
{

class upgrade_mutex
{
public:

    upgrade_mutex();
    ~upgrade_mutex();

    upgrade_mutex(const upgrade_mutex&) = delete;
    upgrade_mutex& operator=(const upgrade_mutex&) = delete;

    // Exclusive ownership

    void lock();  // blocking
    bool try_lock();
    template <class Rep, class Period>
        bool try_lock_for(const std::chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
        bool
        try_lock_until(
                      const std::chrono::time_point<Clock, Duration>& abs_time);
    void unlock();

    // Shared ownership

    void lock_shared();  // blocking
    bool try_lock_shared();
    template <class Rep, class Period>
        bool
        try_lock_shared_for(const std::chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
        bool
        try_lock_shared_until(
                      const std::chrono::time_point<Clock, Duration>& abs_time);
    void unlock_shared();

    // Upgrade ownership

    void lock_upgrade();  // blocking
    bool try_lock_upgrade();
    template <class Rep, class Period>
        bool
        try_lock_upgrade_for(
                            const std::chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
        bool
        try_lock_upgrade_until(
                      const std::chrono::time_point<Clock, Duration>& abs_time);
    void unlock_upgrade();

    // Shared <-> Exclusive

    bool try_unlock_shared_and_lock();
    template <class Rep, class Period>
        bool
        try_unlock_shared_and_lock_for(
                            const std::chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
        bool
        try_unlock_shared_and_lock_until(
                      const std::chrono::time_point<Clock, Duration>& abs_time);
    void unlock_and_lock_shared();

    // Shared <-> Upgrade

    bool try_unlock_shared_and_lock_upgrade();
    template <class Rep, class Period>
        bool
        try_unlock_shared_and_lock_upgrade_for(
                            const std::chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
        bool
        try_unlock_shared_and_lock_upgrade_until(
                      const std::chrono::time_point<Clock, Duration>& abs_time);
    void unlock_upgrade_and_lock_shared();

    // Upgrade <-> Exclusive

    void unlock_upgrade_and_lock();  // blocking
    bool try_unlock_upgrade_and_lock();
    template <class Rep, class Period>
        bool
        try_unlock_upgrade_and_lock_for(
                            const std::chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
        bool
        try_unlock_upgrade_and_lock_until(
                      const std::chrono::time_point<Clock, Duration>& abs_time);
    void unlock_and_lock_upgrade();
};

}  // ting

shared_lock

Header: <shared_mutex>

namespace ting
{

template <class Mutex>
class shared_lock
{
public:
    typedef Mutex mutex_type;

    // Shared locking

    shared_lock();
    explicit shared_lock(mutex_type& m);  // blocking
    shared_lock(mutex_type& m, std::defer_lock_t);
    shared_lock(mutex_type& m, std::try_to_lock_t);
    shared_lock(mutex_type& m, std::adopt_lock_t);
    template <class Clock, class Duration>
        shared_lock(mutex_type& m,
                    const std::chrono::time_point<Clock, Duration>& abs_time);
    template <class Rep, class Period>
        shared_lock(mutex_type& m,
                    const std::chrono::duration<Rep, Period>& rel_time);
    ~shared_lock();

    shared_lock(shared_lock const&) = delete;
    shared_lock& operator=(shared_lock const&) = delete;

    shared_lock(shared_lock&& u);
    shared_lock& operator=(shared_lock&& u);

    void lock();  // blocking
    bool try_lock();
    template <class Rep, class Period>
        bool try_lock_for(const std::chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
        bool
        try_lock_until(
                      const std::chrono::time_point<Clock, Duration>& abs_time);
    void unlock();

    // Conversion from upgrade locking

    explicit shared_lock(upgrade_lock<mutex_type>&& u);

    // Conversion from exclusive locking

    explicit shared_lock(std::unique_lock<mutex_type>&& u);

    // Setters

    void swap(shared_lock& u);
    mutex_type* release();

    // Getters

    bool owns_lock() const;
    explicit operator bool () const;
    mutex_type* mutex() const;
};

}  // ting

upgrade_lock

Header: <shared_mutex>

namespace ting
{

template <class Mutex>
class upgrade_lock
{
public:
    typedef Mutex mutex_type;

    // Upgrade locking

    upgrade_lock();
    explicit upgrade_lock(mutex_type& m);  // blocking
    upgrade_lock(mutex_type& m, std::defer_lock_t);
    upgrade_lock(mutex_type& m, std::try_to_lock_t);
    upgrade_lock(mutex_type& m, std::adopt_lock_t);
    template <class Clock, class Duration>
        upgrade_lock(mutex_type& m,
                     const std::chrono::time_point<Clock, Duration>& abs_time);
    template <class Rep, class Period>
        upgrade_lock(mutex_type& m,
                     const std::chrono::duration<Rep, Period>& rel_time);
    ~upgrade_lock();

    upgrade_lock(upgrade_lock const&) = delete;
    upgrade_lock& operator=(upgrade_lock const&) = delete;

    upgrade_lock(upgrade_lock&& u);
    upgrade_lock& operator=(upgrade_lock&& u);

    void lock();  // blocking
    bool try_lock();
    template <class Rep, class Period>
        bool try_lock_for(const std::chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
        bool
        try_lock_until(
                      const std::chrono::time_point<Clock, Duration>& abs_time);
    void unlock();

    // Conversion from shared locking

    upgrade_lock(shared_lock<mutex_type>&& u, try_to_lock_t);
    template <class Clock, class Duration>
        upgrade_lock(shared_lock<mutex_type>&& u,
                     const std::chrono::time_point<Clock, Duration>& abs_time);
    template <class Rep, class Period>
        upgrade_lock(shared_lock<mutex_type>&& u,
                     const std::chrono::duration<Rep, Period>& rel_time);

    // Conversion from exclusive locking

    explicit upgrade_lock(std::unique_lock<mutex_type>&& u);

    // Setters

    void swap(shared_lock& u);
    mutex_type* release();

    // Getters

    bool owns_lock() const;
    explicit operator bool () const;
    mutex_type* mutex() const;
};

}  // ting

Examples


Given:

std::timed_mutex mut;

To lock it in exclusive mode:

std::lock_guard<std::timed_mutex> lk(mut);

or

std::unique_lock<std::timed_mutex> lk(mut);

To try-lock it in exclusive mode:

std::unique_lock<std::timed_mutex> lk(mut, std::try_to_lock);
if (lk.owns_lock())
   // ...

To try for 30ms:

std::unique_lock<std::timed_mutex> lk(mut, std::chrono::milliseconds(30));
if (lk.owns_lock())
   // ...

Given:

ting::shared_mutex mut;

To lock it in exclusive mode:

std::lock_guard<ting::shared_mutex> lk(mut);

or

std::unique_lock<ting::shared_mutex> lk(mut);

To try-lock it in exclusive mode:

std::unique_lock<ting::shared_mutex> lk(mut, std::try_to_lock);
if (lk.owns_lock())
   // ...

To try for 30ms to lock it in exlusive mode:

std::unique_lock<ting::shared_mutex> lk(mut, std::chrono::milliseconds(30));
if (lk.owns_lock())
   // ...

To lock it in shared mode:

ting::shared_lock<ting::shared_mutex> lk(mut);

To try-lock it in shared mode:

ting::shared_lock<ting::shared_mutex> lk(mut, std::try_to_lock);
if (lk.owns_lock())
   // ...

To try for 30ms to lock it in shared mode:

ting::shared_lock<ting::shared_mutex> lk(mut, std::chrono::milliseconds(30));
if (lk.owns_lock())
   // ...

Given:

ting::upgrade_mutex mut;

To lock it in exclusive mode:

std::lock_guard<ting::upgrade_mutex> E(mut);

or

std::unique_lock<ting::upgrade_mutex> E(mut);

To try-lock it in exclusive mode:

std::unique_lock<ting::upgrade_mutex> E(mut, std::try_to_lock);
if (E.owns_lock())
   // ...

To try for 30ms to lock it in exlusive mode:

std::unique_lock<ting::upgrade_mutex> E(mut, std::chrono::milliseconds(30));
if (E.owns_lock())
   // ...

To lock it in shared mode:

ting::shared_lock<ting::upgrade_mutex> S(mut);

To try-lock it in shared mode:

ting::shared_lock<ting::upgrade_mutex> S(mut, std::try_to_lock);
if (S.owns_lock())
   // ...

To try for 30ms to lock it in shared mode:

ting::shared_lock<ting::upgrade_mutex> S(mut, std::chrono::milliseconds(30));
if (S.owns_lock())
   // ...

To lock it in upgrade mode:

ting::upgrade_lock<ting::upgrade_mutex> U(mut);

To try-lock it in upgrade mode:

ting::upgrade_lock<ting::upgrade_mutex> U(mut, std::try_to_lock);
if (U.owns_lock())
   // ...

To try for 30ms to lock it in upgrade mode:

ting::upgrade_lock<ting::upgrade_mutex> U(mut, std::chrono::milliseconds(30));
if (U.owns_lock())
   // ...

To try-convert shared-ownership to exclusive ownership:

std::unique_lock<ting::upgrade_mutex> E(std::move(S), std::try_to_lock);
if (E.owns_lock())
   // ...

To try for 30ms to convert from shared mode to exclusive mode:

std::unique_lock<ting::upgrade_mutex> E(std::move(S),
                                        std::chrono::milliseconds(30));
if (E.owns_lock())
   // ...

To convert exclusive ownership to shared-ownership:

ting::shared_lock<ting::upgrade_mutex> S(std::move(E));

To try-convert shared-ownership to upgrade ownership:

ting::upgrade_lock<ting::upgrade_mutex> U(std::move(S), std::try_to_lock);
if (U.owns_lock())
   // ...

To try for 30ms to convert from shared mode to upgrade mode:

ting::upgrade_lock<ting::upgrade_mutex> U(std::move(S),
                                          std::chrono::milliseconds(30));
if (U.owns_lock())
   // ...

To convert upgrade ownership to shared-ownership:

ting::shared_lock<ting::upgrade_mutex> S(std::move(U));

To convert upgrade ownership to exlusive ownership:

std::unique_lock<ting::upgrade_mutex> E(std::move(U));

To try-convert upgrade ownership to exlusive ownership:

std::unique_lock<ting::upgrade_mutex> E(std::move(U), std::try_to_lock);
if (E.owns_lock())
   // ...

To try for 30ms to convert from upgrade mode to exclusive mode:

std::unique_lock<ting::upgrade_mutex> E(std::move(U),
                                        std::chrono::milliseconds(30));
if (E.owns_lock())
   // ...

To convert exlusive ownership to upgrade ownership:

ting::upgrade_lock<ting::upgrade_mutex> U(std::move(E));