thread (original) (raw)
Document number: N2320=07-0180
Howard E. Hinnant
Beman Dawes
Lawrence Crowl
Jeff Garland
Anthony Williams
2007-06-24
Multi-threading Library for Standard C++
Contents
Introduction
One of the major thrusts for C++0x is support for multi-threaded programs. The Library Working Group has long agreed to provide basic library facilities such as mutexes, condition variables, and threads based on the existing practice of the Boost Threads library. This proposal provides the Working Paper text for those components. Additional multi-threading components such as atomics and futures are proposed elsewhere.
This proposal is the culmination of a long line of proposals. SeeReferences.
The detailed discussion of motivations, rationales, design alternatives, and other preliminary material remains unchanged from those proposals and is not repeated in the current proposal.
This proposal assumes that exception propagation from threads will be handled as described inN2179or its successors, and so is not included in this proposal.
At the meeting in Oxford, the committee asked Pete, Lawrence and Howard to combine the C interface ofN1907with N2184and produce a new proposal. This effort has only been partially successful. A significant complicating factor is the introduction of cancellation, and the de-facto differences between thread cancellation in C today, and the thread cancellation proposed for C++ in N2184.
Pete producedN2285which is a faithful reproduction of the committee's request. HoweverN2285lacks several of the advances in the mutex, lock and condition types which have emerged over the years based on feedback from the boost experience (N2094). The rationale sections contained herein for these types go into more detail concerning the differences between boost (N1907,N2285) and the current proposal (which closely follows the previousN2094).
An attempt was made to graft the style of the C interface inN1907onto the mutex types ofN2094. It is felt that a key use case for the C level interface is to enable interoperability between C and C++ translation units. An example use case from this early prototype was:
extern "C" void foo();
std::cnd_t cv; std::condition cpp_cv(cv); std::mtx_t mut; std::mutex cpp_mut(mut);
int main() { std::thread t(foo); t.request_cancellation(); t.join(); }
// foo compiled as C
extern cnd_t cv; extern mtx_t mut;
void foo() { mtx_lock(&mut); while (true) cnd_wait(&cv, &mut); mtx_unlock(&mut); }
There are at least two problems with the above sample code:
- The above example requires copying the C mutex types to the C++ mutex types (or vice versa). Prior to this the mutex types have always been non-copyable. The authors of this paper are not comfortable with copyable (or even movable) mutex types.
- condition::wait must be a C++ cancellation point. What happens when a C translation unit is blocked in cnd_wait and a request to cancel comes in? At this point in time we do not feel we can standardize propagating a C++ exception through a C stack frame.
At this point the C/C++ interoperability design outlined inN2145for atomic types was brought to our attention. This approach neatly solves the first problem above by making the C and C++ types layout compatible. The C and C++ translation units can both refer to the same structure, but operate on it with different syntax and even different functions. This was prototyped and the above example use case (for mutex only) simplified to:
// A C++ translation unit
#include
std::mutex m;
void foo() { m.lock(); // do work m.unlock(); }
And in the same program:
// A C translation unit
#include <mutex.h>
extern struct std_mutex m;
void bar() { std_mutex_lock(&m); // do work std_mutex_unlock(&m); }
Now mutex need not be moved, copied, or converted between C and C++ types.
However the second problem above (concerning cancellation) remained. To address this issue this proposal initially stated that the C level interface of condition::waitwould return ECANCELED if it received a cancellation request. Now our example interoperability use case looks like:
// C++ translation unit
std::condition_mtx cv; std::mutex mut; int flag = 0;
extern "C" void f();
int main() { std::thread t(f); t.request_cancellation(); t.join(); }
// C translation unit
extern std_condition cv; extern std_mutex mut; extern int flag;
void f() { std_mutex_lock(&mut); int ec; while (flag == 0) { ec = std_condition_wait(&cv, &mut); if (ec == ECANCELED) // now what?! } std_mutex_unlock(&mut); }
As indicated by the comment in the C function f, once C++ cancellation is detected in a C translation unit, what is the C code supposed to do with it?
The C level interface has been removed from this proposal with the following rationale:
- As long as we specify that the key types in this proposal are standard-layout types (which we have done), WG14 is still free to standardize a C interface which interoperates with this C++ interface.
- WG14 is in a better position to solve the cancellation interoperability problem than WG21 is. We will specify that cancellation is nothing more than a C++ exception. WG14 may in the future standardize a C++-compatibletry/finally which C cancellation can be built on. If this happens we do not want to have an existing C interface specified in the C++ standard which specifies that C cancellation means returning ECANCELLED.
- WG14 asked WG21 to take the lead on this issue. We feel we can best take lead by specifying only a C++ interface which has the minimum hooks in it to support a future C interoperating interface (i.e. types are standard-layout types). We feel we should stop short of actually specifying that C interface in the C++ standard. WG14 can do a better job with the C interface and a future C++ standard can then import it by reference.
We would like to emphasize that despite the removal of the C interoperable interface from this document, we continue to believe that C/C++ interoperability in this area is important. We strongly encourage WG21 and WG14 cooperation in this area. We feel that by specifying the standard layout for the mutex/condition/thread types we have provided the necessary hooks for this interoperability. We would like to see the technical guarantees surrounding this foundation firmed up. And we would like to see prolific communication between WG21 and WG14 on this all important issue.
The thread class proposed herein follows closely to that as described inN2184. This paper has the following differences:
- cancel() has been renamed to request_cancellation() to better describe the intent of this operation.
- The classes thread and thread::id have been specified to have standard layout to facilitate interoperability with a future C interface to be specified by WG14.
- The alias to join(), operator()() has been removed. It is expected that future will have a member named join() to facilitate generic code which can join either with threads or futures.
Creating a thread
A thread is launched by constructing a std::thread with a functor:
#include
void f();
void bar() { std::thread t(f); // f() executes in separate thread }
The functor can be a function, or a more general class with an operator().std::bind can be used to pass arguments to the functor. Any return type is ignored.
#include
struct f { void operator()(const std::string& name, int id) {} };
void bar() { std::thread t(std::bind(f(), "Task A", 5)); // f("Task A", 5) executes in separate thread }
Joining with a thread
A std::thread can be joined with:
t.join(); // wait for thread t to end
One can test if a thread is joinable. If the threadhas already been joined with, or detached, then it is no longer joinable. If the thread has been moved from, it is no longer joinable, unless it has subsequently been moved back into.
std::thread t(f); assert(t.joinable());
std::thread t2(std::move(t)); assert(!t.joinable()); assert(t2.joinable());
t = std::move(t2); assert(t.joinable()); assert(!t2.joinable());
t.join(); assert(!t.joinable());
Note: It is impossible to join with the main thread as one must have astd::thread object to join with. And it is not possible to create astd::thread which refers to the main thread.
Uncaught exceptions
When a thread (other than the main thread) lets an exception go unhandled, the default behavior is to call std::terminate(). However there are many options for easily modifying this default behavior.
void f() { throw 1; }
int main() { std::thread t(f); t.join(); }
// std::terminate() is called after f throws.
If the above is not the desired behavior, the client of f can easily wrapf in a functor using std::bind which catches unhandled exceptions and performs some other action. For example here is such a functor which logs unhandled exceptions, but does not otherwise indicate an error:
#include #include #include
void f() { throw 1; }
struct log_uncaught_exceptions { template void operator()(F f) { try { f(); } catch (...) { std::ofstream("log.text", std::ios::app) << "uncaught exception\n"; } } };
int main() { std::thread t(std::bind(log_uncaught_exceptions(), f)); t.join(); }
The file log.text is appended with "uncaught exception".
Indeed, it is believed that the functor adaptor is sufficiently general that the following can be non-intrusively built upon this proposal:
- future with arbitrary return values.
- future with exception translation/propagation on join.
- shared_future which is copyable and has multi-join functionality.
- thread_pool which queues a large number of functors to be executed using a small number of threads.
thread is move-only
A std::thread is not copyable, but is moveable. This ensures that a threadcan have only one parent or owner, but that ownership can be transferred among scopes, or even among threads.
// factory function for thread std::thread CreateMyThread(const std::string& name, int x) { std::thread t(std::bind(f, name, x)); // maybe wait for / communicate with the new thread here, maybe not... return t; } ... // Details on how you want your thread created are encapsulated std::thread t = CreateMyThread("Task A", 26);
// threads can be stored in containers int main() { std::vectorstd::thread thread_group; thread_group.push_back(std::thread(f)); ... // Number of threads created here not known until run time // (motivating the use of vector in this example) ... // Join with all of the threads for (auto i = thread_group.begin(), e = thread_group.end(); i != e; ++i) i->join(); }
Thread ID
Despite the fact that std::thread is not copyable, its_id_ is copyable. Therefore clients can freely pass around this id. But the only thing this information can be used for is comparing the identity of threads. The id of a thread can be obtained from a joinable std::thread. Additionally a thread can obtain its own id without the use of a std::thread (including the main thread). Finally anid can be default constructed and is then guaranteed not to compare equal to the id of any other running thread.
std::thread::id id; // Refers to no thread assert(id == std::thread::id()); // All default constructed id's compare equal
id = std::this_thread::get_id(); // get id for this thread assert(id != std::thread::id()); // id now refers to this thread
std::thread t(f); // launch a thread and call it t id = t.get_id(); // id now refers to t's id assert(id != std::this_thread::get_id());
this_thread Namespace
Note the use of the this_thread namespace to disambiguate when you are requesting the id for the current thread, vs the id of a child thread. The get_idname for this action remains the same in the interest of reducing the conceptual footprint of the interface. This design also applies to the cancellation_requested function:
std::thread my_child_thread(f); typedef std::thread::id ID:
ID my_id std::this_thread::get_id(); // The current thread's id ID your_id my_child_thread.get_id(); // The child thread's id
bool have_i_been_canceled = std::this_thread::cancellation_requested(); // Current thread's cancellation status bool have_you_been_canceled = my_child_thread.cancellation_requested(); // Child thread's cancellation status
The this_thread namespace also contains a few other functions that operate on, or query the current thread of execution.
Canceling threads
A joinable std::thread can be cooperatively canceled. When a thread cancels, all it does is throw an exception of type thread_canceled. Thus a canceled thread can cancel its cancel simply by catching (and not re-throwing)thread_canceled. One thread can request that another thread throw athread_canceled exception with this syntax:
std::thread t(f); t.request_cancellation(); // request that the thread referred to by t cancel itself
To actually respond to a request to cancel, a thread must execute a cancelation point. A thread can call cancellation_point() in order to turn any code into a cancellation point.
std::this_thread::cancellation_point(); // Will throw a thread_canceled exception if // and only if exceptions are enabled, and if // someone has called t.request_cancellation() where t refers // to this thread
Note that the main thread can not be canceled (by another thread) because cancellation by another thread can only be done through a std::thread and there is no way to create a std::thread which refers to the main thread.
The list of recommended cancellation points is:
- void std::this_thread::cancellation_point()
- template void std::this_thread::sleep(const ElapsedTime& rel_time)
- void std::thread::join()
- void std::condition::wait(Lock&)
- template void std::condition::wait(Lock&, Predicate)
- bool std::condition::timed_wait(Lock&, const utc_time&)
- template bool std::condition::timed_wait(Lock&, const utc_time&, Predicate)
A thread can disable cancellations for itself, even if it does execute a cancellation point such as cancellation_point. This is done with the disable_cancellation class. The construction of this class has the effect of disabling cancellations for the lifetime of the object. When the object destructs, cancellation is automatically reverted to its previous state (typically re-enabled).
{ std::this_thread::disable_cancellation _; ... // cancellation can not happen here std::this_thread::cancellation_point(); // guaranteed to have no effect ... } // cancellation enabled here std::this_thread::cancellation_point(); // Will throw if someone has requested a request_cancellation()
Because cancellation is disabled with a class object, the cancellation is guaranteed to be correctly enabled whether the scope is left normally, or by an exception (whether or not that exception is thread_canceled. Note: within this document, a convention is used that if the object name is "_", that name is not used anywhere.
Cancellation is not disabled during stack unwinding. Destructors must be cancellation safe whether they are being executed due to a thread_canceled exception, or any other exception. Thus automatically disabling cancellation when a thread_canceled is thrown is redundant. And there is at least one corner case where it causes ill effects.
disable_cancellation scopes can be nested. That is, outer code can disable cancellation, and then call other functions, without fear that those functions will disable, and subsequently enable cancellation prematurely.
void foo() { std::this_thread::disable_cancellation _; // cancellation disabled here ... } // cancellation enabled only if it was enabled upon entering foo
void bar() { std::this_thread::disable_cancellation _; // cancellation disabled here foo(); // cancellation still disabled here } // cancellation enabled only if it was enabled upon entering bar
If the main thread calls this_thread::cancellation_point() or constructs an object of typethis_thread::disable_cancellation, there is no effect. The main thread can not be canceled. Having these functions silently ignore the main thread allows library code to use this functionality without worry of whether it is being executed in the main thread or not.
One can request permission from a disable_cancellation object to temporarily re-enable cancellation inside the scope. This requires a non-const disable_cancellationobject:
void foo() { std::this_thread::disable_cancellation no_cancel; A a; // this may not be cancelled std::this_thread::restore_cancellation _(no_cancel); a.foo(); // this may be cancelled } // disable cancellation, a.~A(), restore cancellation
The restore_cancellation constructor simply reverts to the cancellation state that was in effect with the referenced disable_cancellation object was constructed. Thus restore_cancellation doesn't actually enable cancellations unless the referenceddisable_cancellation was not constructed within the scope of another disable_cancellation. Thus when a function says:
void bar() { std::this_thread::disable_cancellation _; foo(); }
then it is not possible for code within the function foo to enable cancellation (even if it tries to as with this example). To enable cancellation in a called function, bar would have to communicate the name of its disable_cancellation object to that function.
This design has been set up to provide flexibility for disabling and enabling cancellation, yet prevent accidental enabling when calling unknown code, and to prevent accidentally not re-enabling when exceptions propagate through a stack frame.
Destructing a thread
Every thread must either be joined or detached within its lifetime. To support cooperative cancellation, the thread destructor must be prepared to deal with threads which have neither been joined or detached. Consider for example a cancelable thread that owns two child threads:
std::thread t1(f1); std::thread t2(f2); t1.join(); t2.join(); // what does t2.~thread() do if t1 throws thread_cancelled?
Upon cancellation of this thread (t1.join() is a cancellation point), athread_canceled exception propagates through the stack frame, destructing t2 before it has had a chance to join. Ift2 simply detaches, then t2 may run for an arbitrarily long time, and consume arbitrarily large resources. This may result in the cancellation request of the parent threadeffectively not being honored. Thus when a thread is destructed, if it is joinable then it is firstcanceled, and then detached. This allows the parent (canceling) thread to continue to cancel without blocking, and yet notify all of its child threads of the cancellation request.
If semantics other than request_cancellation(); detach(); are desired for a threaddestructor, this is easy to arrange for with a thread manager object. Such an object would be a scope guard for a thread which points to the desired functionality upon destruction. Smart pointers with policy destructors are easily and efficiently employed as scope guards.
Threading cooperatively
Namespace this_thread has two functions for yielding processor control to another thread:
void yield(); template void sleep(const ElapsedTime& reltime);
A thread can call these functions to control its own yielding of the processor. There is no way to request another thread to yield or sleep.
Note: See N2328for more details on handling time durations.
Environment
There is one static member function of thread which yields a measure of the number of threads which could possibly execute concurrently:
unsigned n = std::thread::hardware_concurrency();
This can come in handy in a variety of situations such as sizing a thread pool.
Mutex
Mutex Rationale and Examples
Below is shown the basic operation of a mutex. Normally one will want to lock and unlock mutexes using scoped_lock or unique_lock. However lock, try_lock and unlock member functions are available in the mutex types themselves to provide flexibility to the client.
#include
std::mutex m;
void foo() { m.lock(); // do work m.unlock(); }
Mutex concepts
Boost separates mutex concepts out into:
- Mutex
- TryMutex
- TimedMutex
Each of these three concepts have both recursive and non-recursive counterparts for a total of 6 concepts.
- Mutex
- RecursiveMutex
- TryMutex
- RecursiveTryMutex
- TimedMutex
- RecursiveTimedMutex
Because of anticipated support in the future for more mutex concepts (such as read/write) an attempt has been made to reduce the number mutex concepts. It was noted that all mutex concepts can support the TryMutex concept without extra expense. Therefore the TryMutex concept has been eliminated and folded into the Mutex concept:
- Mutex
- RecursiveMutex
- TimedMutex
- RecursiveTimedMutex
It is shown later that the TryMutex concept is a necessary requirement for fundamental generic lock algorithms such as std::lock(L1&, L2&, L3&...) and is thus a good idea to include as a fundamental requirement for all mutex types (high benefit, zero cost).
Time Issues
Most of the time-related interface is based on time durations (e.g. milliseconds(100)) instead of specific points in time (eg: 2007-May-28 00:00:00.12345). The one exception to this policy is the timed_wait member of the condition variable. In this case spurious wake ups are expected, and when this happens, without timing against a specific point in time, it is difficult to know whether you've woken up for spurious reasons or because of a time out, and if for spurious reasons how much longer you need to wait for. Therefore timed_wait on condition variables alone is specified in terms of a specific point in time. Every effort has been made to anticipate the TR2 date_time support and make the standard interface compatible with that. SeeN2328 for details.
Examples
std::timed_mutex mut; std::basic_condition<std::unique_lockstd::mutex> cv;
void foo() { std::unique_lockstd::mutex lk(mut); // Wait for 2 seconds on a condition variable std::utc_time time_out = std::hiresolution_clock::universal_time() + std::seconds(2); while (!pred) { bool timed_out = !cv.timed_wait(lk, time_out); if (timed_out) // deal with time out } }
void bar() { // Wait for 1/10 of a second on a mutex if (mut.timed_lock(std::milliseconds(10))) // got the lock }
Lock Rationale and Examples
Unlike boost locks, the locks proposed herein are not nested types of the mutex classes but class templates which are templated on the mutex type. The locks thus become far more reusable. They can be instantiated with any standard or user-defined mutex which meets the mutex requirements.
std::scoped_lock<**std::mutex**> lock1(m1); // ok ... std::scoped_lock<**std::timed_mutex**> lock2(m2); // also ok ... std::scoped_lock<**my_mutex**> lock3(m3); // also ok
There are two lock class templates:
- template scoped_lock
- template unique_lock
The purpose of scoped_lock is to serve the common use case with as much efficiency as possible. Unlike the boost scoped_lock, thisscoped_lock always owns its referenced mutex. There need be no internal flag indicating ownership. The scoped_lock destructor does not need to perform a test to see if it should unlock the mutex: it unconditionally unlocks the mutex. Thus there is no branch which might stall a processor pipeline just to unlock the mutex.
Using a scoped_lock also easily signals intent: The referenced mutex is locked and unlocked strictly within the containing scope. There is no need for the reader of the code to search for places where mutex ownership might be transferred out of the current scope.
void foo() { std::scoped_lockstd::mutex _(mut); // do protected work // ... } // mut unlocked
It is not possible to have a scoped_lock that does not refer to a mutex. And it is not possible for that referenced mutex to not be locked by thescoped_lock. The only way to lock the mutex is with the scoped_lockconstructor, and the only way to unlock it is with the scoped_lock destructor. This is far more restrictive than the boost scoped_lock, but slightly more efficient as well. Because of the prevalence of the scoped locking pattern, it is felt by the authors that a maximally efficient lock dedicated to this use case is justified.
Because there exist use cases which require more flexibility than a strict scoped style locking pattern, unique_lock is introduced. Unlike scoped_lock, unique_lock may or may not reference a mutex, and if it does, may or may not own the locked state of that mutex. This is much more like the semantics of the boostscoped_lock. However, unlike the boost scoped_lock,unique_lock services all of the mutex concepts (timed and non-timed). A unique_lock is movable, but not copyable, so they can be put into containers and returned from factory functions.
template <class L1, class L2> int try_lock(L1& lock1, L2& lock2) { unique_lock ul(lock1, try_to_lock); if (ul.owns()) { if (lock2.try_lock()) { ul.release(); return -1; } return 1; } return 0; }
In the example above, unique_lock serves to provide exception safety, unlocking lock1 if the attempt to lock lock2throws an exception. However, because strict scoped locking isn't desired in this use case, the unique_lock is asked to release its lock ownership if both lock1 and lock2 are successfully locked.
Also note in the above example that L1 and L2 may also be unique_lock types. Because of the generality of the templated locks (as opposed to being available only as nested types of a mutex), the try_lock algorithm can easily and seamlessly create a unique_lock<unique_lock> type (ul in the example if L1 is a unique_lock).
Finally note a few syntactic differences between boost scoped_lock andunique_lock which lead to improved readability:
- try_to_lock is used to indicate try_lock on construction instead of a bool.
- boost scoped_lock::locked() has been renamed to owns(). Rationale: This member may return false and that does not mean that the referenced mutex is not locked. It means that this unique_lock does not _own_the locked state of the mutex. The mutex may still be locked (say by another thread).
Looking forward, TR2 may have a new lock type that does not model exclusive ownership asscoped_lock and unique_lock do, but rather models shared ownership. A reasonable name for such a lock might be shared_lock.
- template scoped_lock; // Scoped, exclusive ownership
- template unique_lock; // Exclusive ownership
- template shared_lock; // Shared ownership
We feel that the above is an appropriate naming convention for the various lock types.
Generic Locking Algorithm Rationale and Examples
Consider a user written class which contains a data member mutex which controls access to the object:
class Record { mutable std::mutex mut; ... public: ... };
Now consider writing the assignment operator for this class:
Record& Record::operator=(const Record& r) { if (this != &r) { std::scoped_lockstd::mutex this_lock(mut); std::scoped_lockstd::mutex that_lock(r.mut); // Both source and destination are locked // Safe to assign // ... } return *this; }
Unfortunately the above code is wrong and can lead to deadlock. Given two objects of type Record, r1 and r2, if one thread executes r1 = r2 while at the same time another thread executes r2 = r1, then it is possible to deadlock. For example:
Thread A Thread B lock r1.mut lock r2.mut block on r2.mut block on r1.mut
To address this situation a generic locking algorithm is provided which locks an arbitrary number of locks at the same time while avoiding deadlock:
template <class L1, class L2, class ...L3> void lock(L1&, L2&, L3&...);
Now the assignment operator can easily be written to avoid deadlock:
Record& Record::operator=(const Record& r) { if (this != &r) { std::unique_lockstd::mutex this_lock(mut, std::defer_lock); std::unique_lockstd::mutex that_lock(r.mut, std::defer_lock); std::lock(this_lock, that_lock); // Both source and destination are locked // Safe to assign // ... } return *this; }
unique_lock is now required instead of scoped_lock as one can not defer the locking of the mutex within a scoped_lock. Note too that the locks locked with std::lock do not need to be the same type. So if we have read/write locking in the future this might look like:
Record& Record::operator=(const Record& r) { if (this != &r) { std::unique_lockstd::tr2::rw_mutex this_lock(mut, std::defer_lock); std::tr2::shared_lockstd::tr2::rw_mutex that_lock(r.mut, std::defer_lock); std::lock(this_lock, that_lock); // Both source and destination are locked // Safe to assign // ... } return *this; }
In the above example this is write-locked and r is read-locked, all done in a deadlock-safe manner.
Condition Variables
Condition variables are a inter-thread notification mechanism which work closely with mutexes and locks. The typical use case is for a thread to lock a mutex (or lock) associated with some data which is used to compute a predicate (e.g. does the queue have items). When the predicate is false, the thread will wait on the condition variable using the still locked mutex as an argument to the wait function. The locked mutex assures that no other thread can change the protected data while the current thread-of-execution is in the process of blocking (waiting) for that data to be updated. Once the waiting thread is blocked, the system unlocks the mutex so that another thread can lock the mutex, update the protected data, unlock the mutex, and signal the condition variable to wake one or more threads to process the protected data.
Condition Rationale and Examples
std::mutex mut; std::condition_ulm cv; std::queue data;
void thread1() // consumer { while (true) { int d; { std::unique_lockstd::mutex lk(mut); // Protect data while (data.empty()) // Is there data to process? cv.wait(lk); // Sleep and release data lock d = data.front(); // Remove data from non-empty data.pop(); // queue with mutex locked } process(d); // Process data with mutex unlocked } }
void thread2() // producer {
while (true) { int d = get_more_data(); // Produce data with mutex unlocked std::scoped_lockstd::mutex _(mut); // Protect data data.push(d); // get data and push it into queue if (data.size() == 1) cv.notify_one(); // Notify thread1 that data queue has become non-empty } }
The example above demonstrates basic condition usage. The condition type: condition_ulmis a typedef for condition<unique_lock> (the ulm is an acronym for unique_lock<**m**utex>). thread1 acts as a consumer, waiting until there is data in the queue to process. The queue is checked, and data is removed from the queue under the protection of a std::mutex which is locked with a std::unique_lock. While thread1 waits for the empty queue to have data pushed into it, the system unlocks the mutex.
thread2 in the above example supplies data to the queue. As it is accesses the shared data queue it protects it with the std::mutex. When thread2 detects that the queue has transitioned from empty to one element, it signals thread1 via the condition variable. If thread1 isn't blocked on the condition variable at this time, the notification is harmlessly ignored.
Both thread1 and thread2 do as much processing as possible with the std::mutexunlocked, thus increasing overall throughput.
Condition Variable Flexibility
A template class condition is supplied, where the only requirements on Lock are that it support lock() and unlock() member functions.Lock could be any of the standard mutexes or locks, or any user defined mutex or lock (as long as they meet the lock/unlock requirements.
Assuming that TR2 brings read/write mutexes and shared locks, they will be usable with thisstd::condition. Being able to wait with a read/write mutex, locked either for reading or writing, goes significantly beyond Posix capabilities and Boost capabilities (though Windows Vista has this capability).
std::tr2::rw_mutex mut; std::condition<std::tr2::shared_lockstd::tr2::rw_mutex> cv;
void foo() { std::tr2::shared_lockstd::tr2::rw_mutex read_lock(mut); while (there_is_nothing_to_read()) cv.wait(read_lock); ... }
This pattern might allow a single producer, needing a write lock, to signal many consumers which need only read locks to "consume", which finally might signal a single clean up thread needing a write lock to dispose of the data.
Proposed wording
Chapter 30 Multi-threading library
This clause describes components that C++ programs may use to create and manage multi-threaded programs.
The following subclauses describe components to create and manage threads-of-execution, perform mutual exclusion and locking, and communicate between threads-of-execution.
Subclause | Header(s) |
---|---|
Threads | |
Mutexs and locks | |
Condition variables |
Some functions described in this clause are specified to throw exceptions of type system_error([syserr.syserr]). The error_category ([syserr.errcat.overview]) of the error_code reported by such exceptions code()member function is implementation-defined. [Note: The category is typically native_category ([syserr.errcat.overview]) since these error codes usually originate from the underlying operating system application program interface (API). _-- end note_]
Threads
synopsis
namespace std {
class thread_canceled; class thread;
void swap(thread& x, thread& y); void swap(thread&& x, thread& y); void swap(thread& x, thread&& y);
class thread::id; bool operator==(const thread::id& x, const thread::id& y); bool operator!=(const thread::id& x, const thread::id& y);
template<class charT, class traits> basic_ostream<charT, traits>& operator<< (basic_ostream<charT, traits>&& out, const thread::id& id);
namespace this_thread { class disable_cancellation; class restore_cancellation;
void cancellation_point(); bool cancellation_enabled(); bool cancellation_requested();
thread::id get_id();
void yield(); template void sleep(const ElapsedTime& rel_t);
} // this_thread
struct once_flag { constexpr once_flag();
once_flag(const once_flag&) = delete; once_flag& operator=(const once_flag&) = delete; };
template<typename Callable, typename Args...> void call_once(once_flag& flag, Callable func, Args... args);
} // std
Class thread_canceled
An exception class thread_canceled is thrown to execute a cancellation request.[Note: thread_canceleddoes not derive from exception to avoid being caught by accident. --end note]
class thread_canceled { public: thread_canceled(); virtual ~thread_canceled(); virtual const char* what() const; };
thread_canceled()
Effects: Constructs an object of type thread_canceled.
Throws: Nothing.
~thread_canceled()
Effects: Destructs an object of type thread_canceled.
Throws: Nothing.
const char* what()
Returns: An implementation-defined NTBS.
Throws: Nothing.
Remarks: The message may be a null-terminated multibyte string ([multibyte.strings]), suitable for conversion and display as a wstring ([string.classes], [locale.codecvt]). The return value remains valid until the exception object from which it is obtained is destroyed or a non-const member function of the exception object is called.
Class thread
An object of class thread
launches a new thread-of-execution, and provides mechanisms for the current thread-of-execution to wait for completion of the launched thread, request cooperative cancellation of the launched thread, and perform other operations to manage and query the thread's state.
class thread { public: thread(); template explicit thread(F f); ~thread();
thread(const thread&) = delete; thread& operator=(const thread&) = delete;
thread(thread&&); thread& operator=(thread&&);
void swap(thread&&);
void request_cancellation(); bool cancellation_requested() const;
bool joinable() const; void join(); template bool timed_join(const ElapsedTime& rel_t); void detach();
class id { public: id(); friend bool operator==(const id& x, const id& y); friend bool operator!=(const id& x, const id& y); friend bool operator<(const id& _x_, const id& _y_); friend bool operator<=(const id& _x_, const id& _y_); friend bool operator>(const id& x, const id& y); friend bool operator>=(const id& x, const id& y); };
id get_id() const;
typedef implementation-defined native_handle_type; native_handle_type native_handle();
static unsigned hardware_concurrency(); };
Class thread and class thread::id shall be standard-layout types ([?]).
thread();
Effects: Constructs an object of type thread.
Postconditions:
get_id() == thread::id() && joinable() == false
Remarks: get_id() returns an identity that refers to_not any thread_. This identity compares equal to other non-joinable threads, and compares not equal to all other joinable threads.
Throws: Nothing.
template explicit thread(F f)
Requires: If f is an lvalue, F must be CopyConstructible. If f is an rvalue, F must only be MoveConstructible.
Effects: Constructs an object of type thread and executes the functor f asynchronously as a new thread-of-execution. F is a functor which takes no argument. Any return value from the functor is ignored. If f terminates with an uncaught exception of type thread_canceled, or of type publicly derived from thread_canceled, then the effect shall be as if f returned normally. If f terminates with an uncaught exception of any other type, std::terminate() shall be called.
Postconditions:
get_id() != thread::id() && joinable() == true
For the newly created thread-of-execution, this_thread::cancellation_enabled()is true. [Note: cancellation is enabled upon thread creation. -- end note]
*this
represents the newly started thread-of-execution.Throws: system_error if unable to start this thread.
~thread()
Effects: If joinable() then request_cancellation() followed by detach(), otherwise no effects.
Throws: Nothing.
thread(thread&& x)
Effects: Constructs an object of type thread from x.
Postconditions: x.joinable() is false. x.get_id() == thread().get_id(). joinable()returns the value of x.joinable() prior to the start of construction.get_id() returns the value of x.get_id() prior to the start ofconstruction.
Throws: Nothing.
thread& operator=(thread&& x)
Effects: If this currently refers to a joinable thread, callsrequest_cancellation() and detach(). Then assigns the state of x to *thisand sets x to a default constructed state.
Postconditions: x.joinable() is false. x.get_id() == thread().get_id(). joinable() returns the value of x.joinable() prior to the assignment.get_id() returns the value of x.get_id() prior to the assignment.
Throws: Nothing.
void swap(thread&& x)
Effects: Swaps the state of *this and x.
Throws: Nothing.
void request_cancellation()
Preconditions: joinable() is true.
Postcondition: cancellation_requested() is true.
Throws: Nothing.
bool cancellation_requested() const
Preconditions: joinable() is true.
Returns: For the thread-of-execution represented by *this,this_thread::cancellation_requested().
Throws: Nothing.
bool joinable() const
Returns: get_id() != id().
Throws: Nothing.
void join()
Preconditions: joinable() is true.
Effects: The current thread-of-execution blocks until the thread-of-execution represented by
*this
completes.Postconditions: After a normal return of join(),joinable() is false. An exceptional return will indicate that the thread-of-execution represented by
*this
has received a request to cancel. In such an event, the thread-of-execution represented by*this remains unaffected.Throws: If, for the thread-of-execution represented by*this, this_thread::cancellation_requested() becomestrue during the call to join(), throws thread_canceled.
Remarks: This function is a cancellation point for the current thread-of-execution. [Note: The main thread can not be canceled even at a cancellation point. _--end note_]
template bool timed_join(const ElapsedTime& rel_t)
Requires: ElapsedTime shall be explicitly convertible to nanoseconds.
Preconditions: joinable() is true.
Effects: The current thread-of-execution blocks until the the thread-of-execution represented by*this completes, or until the indicated time duration expires.
Postconditions: If timed_join returns true,joinable() shall be false. An exceptional return will indicate that the current thread-of-execution has received a request to cancel. In such an event, the the thread-of-execution represented by*this remains unaffected.
Returns: true if the thread-of-execution represented by*this joined, otherwise false.
Throws: If, for the thread-of-execution represented by*this, this_thread::cancellation_requested() becomestrue during the call to join(), throws thread_canceled.
Remarks: This function is a cancellation point for the current thread-of-execution. [Note: The main thread can not be canceled even at a cancellation point. _--end note_]
void detach()
Preconditions: joinable() is true.
Effects: The thread-of-execution represented by*this continues execution. When the thread-of-execution represented by*this ends execution it shall release any owned resources.
Postconditions: joinable() is false. *thisdoes not represent a thread-of-execution.
Throws: Nothing.
thread::id()
Effects: Constructs an object of type thread::id which compares equal to other default constructed thread::id objects.
Throws: Nothing.
bool operator==(const id& x, const id& y)
Returns: If x and y both represent_not any thread_, then returns true. Otherwise if_x_ and y represent the same thread-of-execution, then returns true. Otherwise returns false.
Throws: Nothing.
bool operator!=(const id& x, const id& y)
Returns: !(x == y)
Throws: Nothing.
bool operator<(const thread_id& x, const thread_id& y)
Returns: Provides an ordering for all objects of type
thread_id
, such that objects of typethread_id
can be used as a key in Associate Containers. For two objects of typethread_id
,x
andy
, ifx == y
returnstrue
, bothx < y
andy < x
shall returnfalse
. Otherwise, precisely one ofx < y
andy < x
shall returntrue
.Throws: Nothing.
bool operator<=(const thread_id& x, const thread_id& y)
Returns: !(y < x)
Throws: Nothing.
bool operator>(const thread_id& x, const thread_id& y)
Returns: y < x
Throws: Nothing.
bool operator>=(const thread_id& x, const thread_id& y)
Returns: !(x < y)
Throws: Nothing.
id get_id() const
Returns: A thread::id which refers to the thread-of-execution represented by*this. If thisthread is not joinable() returns a default constructed id.
Throws: Nothing.
native_handle_type native_handle()
Returns: An implementation defined type representing the underlying OS thread handle.
Throws: Nothing.
unsigned hardware_concurrency()
Returns: The number of threads that can reasonably be expected to execute concurrently. [Note: This value should only be considered to be a hint.--end note] If this value is not computable or well defined a return value of 1 is recommended, but not required.
Throws: Nothing.
void swap(thread& x, thread& y); void swap(thread&& x, thread& y); void swap(thread& x, thread&& y);
Effects: x.swap(y).
template<class charT, class traits> basic_ostream<charT, traits>& operator<< (basic_ostream<charT, traits>&& out, const thread::id& id);
Effects: Inserts an unspecified text representation of the thread::id into the stream out.
Returns: out.
namespace this_thread {
class disable_cancellation { public: disable_cancellation(); ~disable_cancellation();
disable_cancellation(const disable_cancellation&) = delete; disable_cancellation& operator=(const disable_cancellation&) = delete;
}; } // this_thread
disable_cancellation()
Effects: Constructs an object of type disable_cancellation. The construction has the effect of disabling requests to cancel the current thread-of-execution from other threads during the lifetime of this object (except as modified by restore_cancellation). When a cancellation point is executed within the lifetime of this object, a request to cancel has no affect (except as modified by restore_cancellation). The constructor also notes the current cancellation state so that it can be restored at the time this object is destructed.
Throws: Nothing.
Postconditions: this_thread::cancellation_enabled() returns false.
Remarks: This function has no effect if executed from the main thread.
~disable_cancellation()
Effects: Restores the enable-cancellation state to the same as it was when thisdisable_cancellation was constructed.
Throws: Nothing.
Remarks: This function has no effect if executed from the main thread.
namespace this_thread { class restore_cancellation { public: explicit restore_cancellation(disable_cancellation&); ~restore_cancellation();
restore_cancellation(const restore_cancellation&) = delete; restore_cancellation& operator=(const restore_cancellation&) = delete;
}; } // this_thread
restore_cancellation(disable_cancellation& d)
Effects: Constructs an object of type restore_cancellation. The enable-cancellation state is set to the same state that would be observed immediately after d.~disable_cancellation().
Note: The enable-cancellation may not necessarily be enabled if this construction happens within nested scopes of disable_cancellation objects.
Throws: Nothing.
Remarks: This function has no effect if executed from the main thread.
~restore_cancellation()
Effects: Disables cancellation.
Postconditions: this_thread::cancellation_enabled() returns false.
Throws: Nothing.
Remarks: This function has no effect if executed from the main thread.
namespace this_thread { void cancellation_point(); bool cancellation_enabled(); bool cancellation_requested();
thread::id get_id();
void yield(); template void sleep(const ElapsedTime& rel_t);
} // this_thread
void cancellation_point()
Effects: If cancellation_enabled() && cancellation_requested() then throws an exception of type thread_canceled, else there is no effect.
Postconditions: If a thread_canceled is thrown, then cancellation_requested()shall be false.
Throws: thread_canceled.
bool cancellation_enabled()
Returns: If this is the main thread, returns false. Otherwise returns true unless a disable_cancellation object has been constructed (and not destructed) and which has not been reverted with a restore_cancellation object.
Throws: Nothing.
bool cancellation_requested()
Returns: true if request_cancellation() has been called on this thread and the thread has not yet executed a cancellation point with cancellation enabled.
Throws: Nothing.
thread::id this_thread::get_id()
Returns: Returns the id of the current thread. The return shall not be equal to a default constructed thread::id.
Throws: Nothing.
void yield()
Effects: Offers the operating system the chance to schedule another thread.
Throws: Nothing.
template void sleep(const ElapsedTime& rel_t)
Requires: ElapsedTime shall be explicitly convertible to nanoseconds.
Effects: The current thread-of-execution blocks for at least the amount of time specified, unless it receives a request to cancel.
Throws: Nothing.
Remarks: This function is a cancellation point.
struct once_flag
Objects of class
once_flag
are opaque data structures that allowcall_once
to initialize data without causing a data race or deadlock.constexpr once_flag();
Effects: Constructs a object of type
once_flag
.Postcondition: Internal state is set to indicate to an invocation of
call_once
with thisonce_flag
as its initial argument that no function has been called.
non-member function call_once
template<typename Callable, typename Args...> void call_once(once_flag& flag, Callable func, Args... args);
Requires: If the
Callable
argumentfunc
is an lvalue,F
isCopyConstructible
. Otherwise,func
is an rvalue,and F
isMoveConstructible
. Copying or moving (as appropriate) shall have no side effects, and the effect of calling the copy shall be equivalent to calling the original.Effects: The argument
func
(or a copy thereof) is called exactly once for theonce_flag
object specified byflag
, as-if by invokingfunc(args)
,even if
call_once
is called multiple times for the sameonce_flag
object. If multiple calls tocall_once
with the sameonce_flag
object occur in separate threads-of-execution, only one thread shall callfunc
, and none of the threads shall proceed until the call tofunc
has completed. If the invocation offunc
results in an exception being thrown, the exception is propagated to the caller and the effects are as-if this invocation ofcall_once
did not occur.Throws:
system_error
or any exception propagated fromfunc
.Thread safety: Access to the same
once_flag
object by calls tocall_once
from different threads-of-execution shall not result in a data race or deadlock.[Examples:
std::once_flag flag;
void init();
void f() { std::call_once(flag,init); }
struct initializer { void operator()(); };
void g() { static std::once_flag flag2; std::call_once(flag2,initializer()); }
-- end example]
Mutexs and locks
synopsis
namespace std {
struct static_mutex; struct mutex; struct recursive_mutex; struct timed_mutex; struct recursive_timed_mutex;
struct defer_lock_type; struct try_lock_type; struct accept_ownership_type;
extern defer_lock_type defer_lock; extern try_lock_type try_to_lock; extern accept_ownership_type accept_ownership;
class lock_error;
template class scoped_lock; template class unique_lock;
template void swap(unique_lock& x, unique_lock& y); template void swap(unique_lock&& x, unique_lock& y); template void swap(unique_lock& x, unique_lock&& y);
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&...);
} // std
Mutex concepts
Objects of the mutex types enforce mutual exclusion between threads-of-execution by limiting ownership of a mutex object to a single thread-of-execution. A thread-of-execution gets ownership of a mutex object by calling lock() and relinquishes ownership by calling unlock(). Ownership can not be transferred from one thread-of-execution to another. The same thread-of-execution that callslock() for a mutex object must call unlock() for the object. Mutexes can be either recursive or non-recursive. The syntax is the same for both recursive and non-recursive mutexes, but the semantics for the member functions differs as described below.
Each mutex type shall be default constructible and destructible. If the default construction of the Mutex type fails, an exception of type system_error shall be thrown. The destructor of the Mutex type shall not throw an exception. Mutex types are neither copyable nor movable. Each mutex type shall have the following member functions:
void lock();
Precondition: For non-recursive mutexes the current thread-of-execution shall not own the mutex.
Effects: The current thread-of-execution will block until the mutex is not owned by another thread-of-execution. Upon successful completion, the current thread-of-execution owns the mutex.
Throws: system_error.
Thread safety: Calls from different threads-of-execution to lock, try_lock, and unlock functions on an object of a mutex type shall not result in data races or deadlocks.
bool try_lock();
Precondition: For non-recursive mutexes the current thread-of-execution shall not own the mutex.
Effects: If ownership can be obtained without blocking, then ownership is obtained, else there is no effect and try_lock() immediately returns.
Returns: true if ownership was obtained, otherwise false.
Thread safety: Calls from different threads-of-execution to lock, try_lock, and unlock functions on an object of a mutex type shall not result in data races or deadlocks.
Throws: Nothing.
void unlock();
Precondition: The current thread-of-execution shall own the mutex.
Effects: For a non-recursive mutex ownership is released. For a recursive mutexunlock() must be called the same number of times which the mutex was locked (via either lock() or try_lock() or by any other locking function) before ownership is released.
Thread safety: Calls from different threads-of-execution to lock, and try_lock functions on an object of a mutex type shall not result in data races or deadlocks.
Throws: Nothing.
If and only if the mutex type is internally represented by a single data structure which can be passed to operating system specific interfaces, then there shall be a nested implementation-defined typedef native_handle_type that is an alias to this native type if it is copyable, otherwise if the native type is not copyable, is a pointer to this native type. The implementation shall document whether or not the native_handle_typetypedef is present.
If the nested typedef native_handle_type exists, then there also shall be a member function native_handle() which returns a handle to this internal data structure. [Example:
class mutex { pthread_mutex_t m; public: typedef pthread_mutex_t* native_handle_type; native_handle_type native_handle() {return &m;} ... };
--end example]
If there is no single operating system specific data structure which implements the mutex type, then neither the nested type native_handle_type nor the member function native_handle() shall not be present. _[Example:_if a recursive_mutex is implemented with both a pthread_mutex_tand a separate lock count, then there will be no native_handle_type. --end example]
Implementations may supply additional implementation defined constructors which allow further customization as afforded by the implementation or its environment.
Class static_mutex
The class static_mutex is based on a new language feature constexprwhich is not yet in the working draft, nor do we have field experience with it. Should this language feature fail to deliver the static initialization behavior desired, we recommend removing static_mutex from the working paper.
namespace std {
struct static_mutex { public: constexpr static_mutex(); ~static_mutex();
static_mutex(const static_mutex&) = delete; static_mutex& operator=(const static_mutex&) = delete;
void lock(); bool try_lock(); void unlock();
typedef unspecified native_handle_type; // conditionally present. example: pthread_mutex_t* native_handle_type native_handle(); // conditionally present };
} // std
The class static_mutex
is a non-recursive mutex. It shall be a standard-layout type ([?]), and does not require dynamic initialization. The default constructor, if dynamically initialized, shall not throw an exception.
Class mutex
namespace std {
struct mutex { public: mutex(); ~mutex();
mutex(const mutex&) = delete; mutex& operator=(const mutex&) = delete;
void lock(); bool try_lock(); void unlock();
typedef unspecified native_handle_type; // conditionally present. example: pthread_mutex_t* native_handle_type native_handle(); // conditionally present };
} // std
The class mutex is a non-recursive mutex which satisfies all of the Mutex requirements. It shall be a standard-layout type ([?]).
Class recursive_mutex
namespace std {
struct recursive_mutex { public: recursive_mutex(); ~recursive_mutex();
recursive_mutex(const recursive_mutex&) = delete; recursive_mutex& operator=(const recursive_mutex&) = delete;
void lock(); bool try_lock(); void unlock();
typedef unspecified native_handle_type; // conditionally present. example: pthread_mutex_t* native_handle_type native_handle(); // conditionally present };
} // std
The class recursive_mutex shall be a recursive mutex which satisfies all of the Mutex requirements. It shall be a standard-layout type ([?]).
Timed Mutexes
Types that meet the requirements of the Timed Mutex concept also meet the requirements of the Mutex concept and add a single member function:
template bool timed_lock(const ElapsedTime& rel_time);
Precondition: For non-recursive mutexes the current thread-of-execution shall not own the mutex. The type ElapsedTime shall be explicitly convertible to nanoseconds.
Effects: The function attempts to obtain ownership of the mutex within the specified time. If the indicated time is less than or equal to 0, the function still attempts to obtain ownership without blocking (as if by calling try_lock()). If the function returns within the specified time duration, it shall have obtained ownership.
Returns: true if ownership was obtained, otherwise false.
Thread safety: Calls to this member function from different threads-of-execution shall not result in data races or deadlocks.
Throws: Nothing.
Class timed_mutex
namespace std {
struct timed_mutex { public: timed_mutex(); ~timed_mutex();
timed_mutex(const timed_mutex&) = delete; timed_mutex& operator=(const timed_mutex&) = delete;
void lock(); bool try_lock(); template bool timed_lock(const ElapsedTime& rel_time); void unlock();
typedef unspecified native_handle_type; // conditionally present. example: pthread_mutex_t* native_handle_type native_handle(); // conditionally present };
} // std
The class timed_mutex is a non-recursive mutex that satisfies all of the Timed Mutex requirements. It shall be a standard-layout type ([?]).
Class recursive_timed_mutex
namespace std {
struct 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;
void lock(); bool try_lock(); template bool timed_lock(const ElapsedTime& rel_time); void unlock();
typedef unspecified native_handle_type; // conditionally present. example: pthread_mutex_t* native_handle_type native_handle(); // conditionally present };
} // std
The class recursive_timed_mutex shall be a recursive mutex that satisfies all of the Timed Mutex requirements. It shall be a standard-layout type ([?]).
Locks
Locks are objects that hold a reference to a mutex and unlock the mutex during the lock's destruction (such as when leaving block scope). The locks do not manage the lifetime of the mutex they reference, but only the ownership status of that mutex. [Note: Locks are intended to ease the burden of unlocking the mutex under both normal and exceptional circumstances. --end note]
Some locks may take tag types which describe what should be done with the mutex in the lock's constructor.
struct defer_lock_type {}; struct try_lock_type {}; struct accept_ownership_type {};
extern defer_lock_type defer_lock; extern try_lock_type try_to_lock; extern accept_ownership_type accept_ownership;
An exception class lock_error derives from exception and is used to indicate improper usage of locks such as locking a mutex that the lock already owns, or unlocking a mutex that the lock does not own.
class lock_error : public std::exception { public: virtual const char* what() const throw(); };
Class scoped_lock
namespace std {
template class scoped_lock { public: typedef Mutex mutex_type;
explicit scoped_lock(mutex_type& m); scoped_lock(mutex_type& m, accept_ownership_type); ~scoped_lock();
scoped_lock(scoped_lock const&) = delete; scoped_lock& operator=(scoped_lock const&) = delete;
constexpr bool owns() const; };
} // std
scoped_lock is used to control the ownership of a mutex within a single scope. An invariant of the scoped_lock object is that it maintains the ownership of the mutex throughout the scoped_lock's lifetime. Mutex ownership can not be deferred or transferred away from the scoped_lock.
explicit scoped_lock(mutex_type& m);
Precondition: If mutex_type is not a recursive mutex, the current thread-of-execution does not own the mutex. The lifetime of m is greater than the lifetime of the
scoped_lock
object.Effects: Stores a reference to m and calls m.lock().
scoped_lock(mutex_type& m, accept_ownership_type);
Precondition: The current thread-of-execution has ownership of the mutex m. The lifetime of m is greater than the lifetime of the
scoped_lock
object.Effects: Stores a reference to m and performs no other operation on it.
~scoped_lock();
Effects: m.unlock().
Throws: Nothing.
constexpr bool owns() const;
Returns: true.
Throws: Nothing.
Class unique_lock
namespace std {
template class unique_lock { public: typedef Mutex mutex_type;
unique_lock(); explicit unique_lock(mutex_type& m); unique_lock(mutex_type& m, defer_lock_type); unique_lock(mutex_type& m, try_lock_type); unique_lock(mutex_type& m, accept_ownership_type); ~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(); bool try_lock(); template bool timed_lock(const ElapsedTime& rel_t); void unlock();
bool owns() const; operator unspecified-bool-type () const; mutex_type* mutex() const;
void swap(unique_lock&& u); mutex_type* release(); };
template void swap(unique_lock& x, unique_lock& y); template void swap(unique_lock&& x, unique_lock& y); template void swap(unique_lock& x, unique_lock&& y);
} // std
unique_lock is used to control the ownership of a mutex within one or more scopes. Mutex ownership can be deferred or transferred away from the unique_lock. An object of typeunique_lock is not copyable but is movable.
unique_lock();
Effects: Constructs an object of type unique_lock.
Postcondition:
mutex() == 0 owns() == false
explicit unique_lock(mutex_type& m);
Precondition: If mutex_type is not a recursive mutex, the current thread-of-execution does not own the mutex. The lifetime of m is greater than the lifetime of the unique_lock object.
Effects: Stores a reference to m and calls m.lock().
Postcondition:
mutex() == &m owns() == true
unique_lock(mutex_type& m, defer_lock_type);
Precondition: If mutex_type is not a recursive mutex, the current thread-of-execution does not own the mutex. The lifetime of m is greater than the lifetime of the unique_lock object.
Effects: Stores a reference to m and performs no other operation on it.
Postcondition:
mutex() == &m owns() == false
unique_lock(mutex_type& m, try_lock_type);
Precondition: If mutex_type is not a recursive mutex, then the current thread-of-execution does not own the mutex. The lifetime of m is greater than the lifetime of the unique_lock object.
Effects: Stores a reference to m and calls m.try_lock().
Postcondition:
mutex() == &m owns() == The result of the call to m.try_lock()
unique_lock(mutex_type& m, accept_ownership_type);
Precondition: The current thread-of-execution has ownership of the mutex m. The lifetime of m is greater than the lifetime of the unique_lock object.
Effects: Stores a reference to m and performs no other operation on it.
Postcondition:
mutex() == &m owns() == true
~unique_lock();
Effects: If owns() calls unlock() on the referenced mutex. Otherwise there are no effects.
Throws: Nothing.
unique_lock(unique_lock&& u);
Effects: Transfers mutex ownership (if any) from u to this.
Postcondition:
mutex() == The value of u.mutex() prior to the construction. owns() == The value of u.owns() prior to the construction. u.mutex() == 0 u.owns() == false
Throws: Nothing.
unique_lock& operator=(unique_lock&& u);
Effects: If owns() calls unlock(), and then transfers mutex ownership (if any) from u to this.
Postcondition:
mutex() == The value of u.mutex() prior to the construction. owns() == The value of u.owns() prior to the construction. u.mutex() == 0 u.owns() == false
Throws: Nothing.
Note: With a recursive mutex it is possible that boththis and u own the same mutex before the assignment. In this case, this will own the mutex after the assignment (andu will not), but the mutex's lock count will be decremented by one.
void lock();
Effects: Calls lock() on the referenced mutex.
Postcondition: owns() == true.
Throws: lock_error, if on entry owns() is true.
bool try_lock();
Effects: Calls try_lock() on the referenced mutex.
Returns: The result of the call to try_lock() on the referenced mutex.
Postcondition: owns() == The result of the call to try_lock() on the referenced mutex.
Throws: lock_error, if on entry owns() is true.
template bool timed_lock(const ElapsedTime& rel_t);
Effects: Calls timed_lock(rel_t) on the referenced mutex.
Returns: The result of the call to timed_lock(rel_t) on the referenced mutex.
Postcondition: owns() == The result of the call to timed_lock(rel_t) on the referenced mutex.
Throws: lock_error, if on entry owns() is true.
void unlock();
Effects: Calls void unlock() on the referenced mutex.
Postcondition: owns() == false.
Throws: lock_error, if on entry owns() is false.
bool owns() const;
Returns: true if this owns a lock on a referenced mutex, else false.
Throws: Nothing.
operator unspecified-bool-type () const;
Returns: Non-null if owns() would return true, else returns null.
Throws: Nothing.
mutex_type* mutex() const;
Returns: A pointer to the referenced mutex, or null if there is no referenced mutex.
Throws: Nothing.
void swap(unique_lock&& u);
Effects: Swaps state with u.
Throws: Nothing.
mutex_type* release();
Returns: A pointer to the referenced mutex, or null if there is no referenced mutex.
Postcondition:
mutex() == 0 owns() == false
Throws: Nothing.
template void swap(unique_lock& x, unique_lock& y); template void swap(unique_lock&& x, unique_lock& y); template void swap(unique_lock& x, unique_lock&& y);
Effects: x.swap(y).
Throws: Nothing.
Generic Locking Algorithms
template <class L1, class L2, class ...L3> int try_lock(L1&, L2&, L3&...);
Requires: Each template parameter type must supply the following member functions with semantics corresponding to the Mutex concept, except that try_lock is allowed to throw an exception. [Note: The unique_lock class template meets these requirements when suitable instantiated. --end note]
bool try_lock(); void unlock();
Effects: The functions attempts to lock all arguments without blocking by calling try_lock()on each of them. If any argument can not be locked, then all arguments which have already been locked will be unlocked. On return, either all arguments will be locked, or none of them will be locked. If an exception is thrown by a call to try_lock(), there are no effects.
Returns: If all arguments were successfully locked, returns
-1
. Otherwise returns a 0-based index value indicating which argument failed to lock.
template <class L1, class L2, class ...L3> void lock(L1&, L2&, L3&...);
Requires: Each template parameter type must supply the following member functions with semantics corresponding to the Mutex concept, except that try_lock is allowed to throw an exception [Note: The unique_lock class template meets these requirements when suitable instantiated. --end note]
void lock(); bool try_lock(); void unlock();
Effects: All arguments are locked with an algorithm that avoids deadlock. If an exception is thrown by a call to lock() or try_lock(), there are no effects.
Condition variables
synopsis
namespace std {
template class condition;
typedef condition condition_mtx; typedef condition<unique_lock> condition_ulm;
} // std
Class template condition
An object of class template condition is a synchronization primitive used to cause a thread-of-execution to wait until notified by some other thread-of-execution that some condition is met, or a UTC[(?)] time is reached.
The Lock type must support member functions lockand unlock with the semantics of the mutex concept. All of the standard mutex types meet this requirement. Additionally Lock may provide an owns() signature returning bool as outlined for the unique_lock class template. If present, the condition class template will use this member for error checking.
namespace std {
template class condition { public: typedef Lock lock_type;
condition(); ~condition();
condition(const condition&) = delete; condition& operator=(const condition&) = delete;
void notify_one(); void notify_all(); void wait(lock_type& lock); template void wait(lock_type& lock, Predicate pred); bool timed_wait(lock_type& lock, const utc_time& abs_time); template bool timed_wait(lock_type& lock, const utc_time& abs_time, Predicate pred); };
} // std
condition();
Effects: Constructs an object of class condition.
~condition();
Effects: Destroys the object.
Throws: Nothing.
void notify_one();
Effects: If any threads-of-execution are blocked waiting for
*this
, unblocks at least one those threads.Thread safety: Calls to the
wait
,timed_wait
,notify_one
ornotify_all
member functions of the same conditionobject from different threads-of-execution shall not result in data races or deadlocks.
void notify_all();
Effects: Unblock all threads that are blocked waiting for
*this
.Thread safety: Calls to the
wait
,timed_wait
,notify_one
ornotify_all
member functions of the same conditionobject from different threads-of-execution shall not result in data races or deadlocks.
void wait(lock_type& lock);
Precondition: lock is locked by the current thread-of-execution. Iflock_type supports recursive locking, the lock count is one. No other thread-of-execution is waiting on this condition object unless lockis, or refers to, the same underlying mutex object.
Effects: Atomically blocks and releases the lock on lock. If the thread-of-execution is canceled while blocked, lockwill be locked as the thread_canceled exception propagates out. This thread-of-execution shall unblock when another thread issues a notification to this blocked thread. The current thread-of-execution may unblock and return even in the absence of a notification.
Postcondition: lock is locked by the current thread-of-execution.
Throws: thread_canceled, system_error. Iflock_type has an owns() member function andlock.owns() returns false upon entry, a lock_error is thrown.
Thread safety: Calls to the
wait
,timed_wait
,notify_one
ornotify_all
member functions of the same conditionobject from different threads-of-execution shall not result in data races or deadlocks.Remarks: This function is a cancellation point for the calling thread. [Note: The main thread can not be canceled even at a cancellation point. _--end note_]
template void wait(lock_type& lock, Predicate pred);
Effects: While pred() returns false calls wait(lock).
Note: There is no blocking if pred() is initially true.
bool timed_wait(lock_type& lock, const utc_time& abs_time);
Precondition: The lock is locked by the current thread-of-execution. Iflock_type supports recursive locking, the lock count is one. No other thread-of-execution is waiting on this condition object unless lockis, or refers to, the same underlying mutex object.
Effects: Atomically blocks and releases the lock on lock. If the thread-of-execution is canceled while blocked, lockwill be locked as the thread_canceled exception propagates out. If the absolute time specified by abs_time passes (that is, system time equals or exceeds abs_time) before the condition is notified, or if the absolute time specified by abs_time has already been passed at the time of the call, then false is returned. This thread-of-execution shall unblock when another thread issues a notification to this blocked thread. The current thread-of-execution may unblock and return even in the absence of a notification.
Postcondition: lock is locked by the current thread-of-execution.
Returns: true if the call to timed_wait is notified prior to the indicated timeout, otherwise returns false.
Throws: thread_canceled, system_error. Iflock_type has an owns() member function andlock.owns() returns false upon entry, a lock_error is thrown.
Thread safety: Calls to the
wait
,timed_wait
,notify_one
ornotify_all
member functions of the same conditionobject from different threads-of-execution shall not result in data races or deadlocks.Remarks: This function is a cancellation point for the calling thread. [Note: The main thread can not be canceled even at a cancellation point. _--end note_]
template bool timed_wait(lock_type& lock, const utc_time& abs_time, Predicate pred);
Effects: As if:
while (!pred()) { if (!timed_wait(lock, abs_time)) return pred(); } return true;
Returns: pred().
Note: There is no blocking if pred() is initially true, even if the timeout has already expired. The return value indicates whether the predicate evaluates to true, regardless of whether the timeout was triggered.
The specialization condition shall be a standard-layout type ([?]).
References
- N1682, A Multi-threading Library for Standard C++, Pete Becker.
- N1815, ISO C++ Strategic Plan for Multithreading, Lawrence Crowl.
- N1883, Preliminary Threading Library Proposal for TR2, Kevlin Henney.
- N1907, A Multi-threading Library for Standard C++, Revision 1 Pete Becker
- N2043, Simplifying And Extending Mutex and Scoped Lock Types For C++ Multi-Threading Library, Ion GaztaƱaga
- N2090, A Threading API for C++, Peter Dimov
- N2094, Multithreading API for C++0X - A Layered Approach, Howard Hinnant
- N2139, Thoughts on a Thread Library for C++, Anthony Williams
- N2178, Proposed Text for Chapter 30, Thread Support Library, Peter Dimov
- N2184, Thread Launching for C++0X, Howard Hinnant
- N2285, A Multi-threading Library for Standard C++, Revision 2, Pete Becker
Acknowledgments
The overall design of this threading library is based on William Kempf's Boost.Thread Library, as refined by literally hundreds of other Boost users and contributors. Dinkumware and Metrowerks (now Freescale) implementations of Boost.Thread, developed respectively by Pete Becker and Howard Hinnant, created further existing practice. Proposals by Pete Becker, Peter Dimov, Ion GaztaƱaga, and Anthony Williams were also influential. Peter, Ion, and Anthony also contributed numerous critiques, suggestions, and comments on the current proposal, as did other members of an ad hoc threads working group.