Handling mutexes in C++
来源:互联网 发布:淘客程序源码一键部署 编辑:程序博客网 时间:2024/06/06 14:15
转载地址:http://web.archive.org/web/20140531071228/http://home.roadrunner.com/~hinnant/mutexes/locking.html
Handling mutexes in C++ is an excellent tutorial. You need just replace std and ting by boost.
Contents
- Introduction
- Exclusive Locking
- Why is
lock_guard
templated? unique_lock
tutorial
- Why is
- Shared Locking
- Upgrade Locking
- Reference Synopses
mutex
recursive_mutex
timed_mutex
recursive_timed_mutex
lock_guard
unique_lock
- Multi-lock locking
shared_mutex
upgrade_mutex
shared_lock
upgrade_lock
- Examples
- Performance Testing
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:
std::mutex
std::lock_guard<std::mutex>
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>voidsub_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>voidprint(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>voidprint(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>voidprint(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:
lhs_lock
and rhs_lock are constructed without lockingmut_
andrhs.mut_
. Theseunique_lock
s merely reference the mutexes.std::lock(lhs_lock, rhs_lock)
is used to lock both mutexes at once, without deadlock. That's its job.- If an exception is thrown while assigning the data, the destructors of
lhs_lock
and rhs_lock ensure thatmut_
andrhs.mut_
are unlocked before leaving the scope. lock_guard
can not be used here because there are points in this algorithm where the lock does not own the locking mode of the referenced mutex. If you attempted to construct alock_guard
withstd::defer_lock
it would not compile.
The biggestdifference 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:
unique_lock
is move constructible. It leaves its source in a state that does not reference a mutex.lock_guard
is not move constructible.Neither lock is copy constructible.unique_lock
can be constructed withstd::defer_lock
, which leaves it referencing a mutex, but not owning the locked state of the mutex.lock_guard
can not be constructed withstd::defer_lock
.unique_lock
can be constructed withstd::try_to_lock
, which may or may not result in the lock owning the locked state of the referenced mutex.lock_guard
can not be constructed withstd::try_to_lock
.unique_lock
haslock()
andunlock()
member functions.lock_guard
does not have these member functionsbecause the referenced mutex is always locked
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.
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:
lock_shared
try_lock_shared
try_lock_shared_for
try_lock_shared_until
unlock_shared
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=
:
A
holds ating::shared_mutex
which can belocked either in exclusive mode or shared mode.- In
operator= mut_
is locked in exclusive mode using astd::unique_lock
andrhs.mut_
is locked in shared mode using ating::shared_lock
. std::lock
is used to lock both the exclusive lock and the shared lock. It doesn't care what it is locking. It will do job, and is still required to avoid deadlock.- Allows several threads to concurrently assign from the same source, while requiring threads to synchronize when trying to assign to the same target.
shared_lock
can also come in handy in implementingA
's copy constructor
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
.
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.(Upgrade Locking 的概念)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.A
's and averages them, replacing both with the average, illustrates this point: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 ofclass 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_; }};
ting::upgrade_mutex
. The above example is calling (under the covers) the following member functions of ting::upgrade_mutex
:lock
lock_upgrade
unlock
unlock_upgrade
unlock_and_lock_shared
unlock_upgrade_and_lock
unlock_shared
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.
Question: What if I don't know until run time which thread I want to upgrade from shared ownership to exclusive ownership?// Introduce try_lock_until utilitytemplate <class Clock, class Duration, class L0, class L1>inttry_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>inttry_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.boolaverage(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;}
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:
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));
- Handling mutexes in C++
- Mutexes in Oracle Database
- About Error handling in C
- The Interrupt handling in Korn shell and C Shell
- event handling in c#
- handling faults in WCF
- Exception Handling in C#
- Error handling in WCF
- Exception Handling in C#
- signal handling in thread
- Error Handling In VBA
- Cookie Handling in WinHTTP
- Handling Events in Windows
- Error handling in VBScript
- Exception handling in java
- Interrupt handling in ARM
- event handling in Java
- file handling in python
- iOS中代码调用button方法
- 内存的划分整理
- sessionStorage localStorage 和 cookie 之间的异同
- Python的学习笔记DAY10---关于正则表达式
- SpringMVC 解读——<mvc:annotation-driven/>
- Handling mutexes in C++
- 如何选用靠谱短信服务商
- web前端面试题之html+css+js
- 老鼠吃奶酪
- javaweb面试题
- maven设置------setting.xml文件学习
- Flume TailDir 基本流程
- .NET 4.0下使用 SignalR
- UIDocumentInteractionController之程序间文档共享