Multi-threading Library for Standard C++

 

Contents

Introduction

One of the majorthrusts for C++0x is support for multi-threaded programs. The Library WorkingGroup has long agreed to provide basic library facilities such as mutexes,condition variables, and threads based on the existing practice of the BoostThreads library. This proposal provides the Working Paper text for thosecomponents. Additional multi-threading components such as atomics and futuresare proposed elsewhere.

This proposal is theculmination of a long line of proposals. See References.

The detaileddiscussion of motivations, rationales, design alternatives, and otherpreliminary material remains unchanged from those proposals and is not repeatedin the current proposal.

This proposalassumes that exception propagation from threads will be handled as described inN2179or its successors, and so is not included in this proposal.

At the meeting inOxford, the committee asked Pete, Lawrence and Howard to combine the Cinterface of N1907with N2184and produce a new proposal. This effort has only been partially successful. Asignificant complicating factor is the introduction of cancellation, and thede-facto differences between thread cancellation in C today, and the threadcancellation proposed for C++ in N2184.

Pete produced N2285which is a faithful reproduction of the committee's request. However N2285lacks several of the advances in the mutex, lock and condition types which haveemerged over the years based on feedback from the boost experience (N2094).The rationale sections contained herein for these types go into more detailconcerning the differences between boost (N1907,N2285)and the current proposal (which closely follows the previous N2094).

An attempt was madeto graft the style of the C interface in N1907onto the mutex types of N2094.It is felt that a key use case for the C level interface is to enableinteroperability between C and C++ translation units. An example use case fromthis early prototype was:

extern"C" void foo();

std::cnd_tcv;
std::condition cpp_cv(cv);
std::mtx_t mut;
std::mutex cpp_mut(mut);

intmain()
{
   std::thread t(foo);
   t.request_cancellation();
   t.join();
}

//foo compiled as C

externcnd_t cv;
extern mtx_t mut;

voidfoo()
{
  mtx_lock(&mut);
  while (true)
      cnd_wait(&cv, &mut);
  mtx_unlock(&mut);
}

There are at leasttwo problems with the above sample code:

  1. 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.
  1. 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 theC/C++ interoperability design outlined in N2145for atomic types was brought to our attention. This approach neatly solves thefirst problem above by making the C and C++ types layout compatible. The C andC++ translation units can both refer to the same structure, but operate on itwith different syntax and even different functions. This was prototyped and theabove example use case (for mutex only) simplified to:

//A C++ translation unit

#include<mutex>

std::mutexm;

voidfoo()
{
    m.lock();
    // do work
    m.unlock();
}

And in the sameprogram:

//A C translation unit

#include<mutex.h>

externstruct std_mutex m;

voidbar()
{
    std_mutex_lock(&m);
    // do work
    std_mutex_unlock(&m);
}

Now mutex need notbe moved, copied, or converted between C and C++ types.

However the secondproblem above (concerning cancellation) remained. To address this issue thisproposal initially stated that the C level interface of condition::wait wouldreturn ECANCELED if it received a cancellation request. Now our exampleinteroperability use case looks like:

//C++ translation unit

std::condition_mtxcv;
std::mutex mut;
int flag = 0;

extern"C" void f();

intmain()
{
   std::thread t(f);
   t.request_cancellation();
   t.join();
}

//C translation unit

externstd_condition cv;
extern std_mutex mut;
extern int flag;

voidf()
{
   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 thecomment in the C function f, once C++ cancellation is detected in a Ctranslation unit, what is the C code supposed to do with it?

The C levelinterface 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++-compatible try/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 toemphasize that despite the removal of the C interoperable interface from thisdocument, we continue to believe that C/C++ interoperability in this area isimportant. We strongly encourage WG21 and WG14 cooperation in this area. Wefeel that by specifying the standard layout for the mutex/condition/threadtypes we have provided the necessary hooks for this interoperability. We wouldlike to see the technical guarantees surrounding this foundation firmed up. Andwe would like to see prolific communication between WG21 and WG14 on this allimportant issue.

Thread

The thread classproposed herein follows closely to that as described in N2184.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 launchedby constructing a std::thread with a functor:

#include<thread>

voidf();

voidbar()
{
    std::thread t(f);  // f() executes in separate thread
}

The functor can be afunction, or a more general class with an operator(). std::bind can be used topass arguments to the functor. Any return type is ignored.

#include<thread>

structf
{
    void operator()(conststd::string& name, int id) {}
};

voidbar()
{
    std::thread t(std::bind(f(),"Task A", 5));  // f("TaskA", 5) executes in separate thread
}

Joining with a thread

A std::thread can bejoined with:

t.join();  // wait for thread t to end

One can test if athread is joinable. If the thread hasalready been joined with, or detached, then it is no longer joinable. If thethread has been moved from, it is no longer joinable, unless it hassubsequently been moved back into.

std::threadt(f);
assert(t.joinable());

std::threadt2(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 isimpossible to join with the main thread as one must have a std::thread objectto join with. And it is not possible to create a std::thread which refers tothe main thread.

Uncaught exceptions

When a thread (otherthan the main thread) lets an exception go unhandled, the default behavior isto call std::terminate(). However there are many options for easily modifyingthis default behavior.

voidf()
{
    throw 1;
}

intmain()
{
    std::thread t(f);
    t.join();
}

//std::terminate() is called after f throws.

If the above is notthe desired behavior, the client of f can easily wrap f in a functor usingstd::bind which catches unhandled exceptions and performs some other action.For example here is such a functor which logs unhandled exceptions, but doesnot otherwise indicate an error:

#include<fstream>
#include <thread>
#include <functional>

voidf()
{
    throw 1;
}

structlog_uncaught_exceptions
{
    template <class F>
    void operator()(F f)
    {
        try
        {
            f();
        }
        catch (...)
        {
           std::ofstream("log.text", std::ios::app) <<"uncaught exception\n";
        }
    }
};

intmain()
{
    std::threadt(std::bind(log_uncaught_exceptions(), f));
    t.join();
}

Thefile log.text is appended with "uncaught exception".

Indeed, it isbelieved that the functor adaptor is sufficiently general that the followingcan be non-intrusively built upon thisproposal:

  • 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 notcopyable, but is moveable. This ensures that a thread can have only one parentor owner, but that ownership can be transferred among scopes, or even amongthreads.

//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 withthe new thread here, maybe not...
    return t;
}
...
// Details on how you want your thread created areencapsulated
std::thread t = CreateMyThread("Task A", 26);

//threads can be stored in containers
int main()
{
    std::vector<std::thread>thread_group;
   thread_group.push_back(std::thread(f));
    ...
    // Number of threads created here notknown until run time
    // (motivating the use ofvector<thread> in this example)
    ...
    // Join with all of thethreads
    for (auto i = thread_group.begin(), e= thread_group.end(); i != e; ++i)
        i->join();
}

Thread ID

Despite the factthat std::thread is not copyable, its idis copyable. Therefore clients canfreely pass around this id. But the only thing this information can be used foris comparing the identity of threads. The id of a thread can be obtained from ajoinable std::thread. Additionally a thread can obtain its own id without theuse of a std::thread (including the main thread). Finally an id can be defaultconstructed and is then guaranteed not to compare equal to the id of any otherrunning thread.

std::thread::idid;               // Refers to nothread
assert(id == std::thread::id());  // Alldefault constructed id's compare equal

id= std::this_thread::get_id();  // get idfor this thread
assert(id != std::thread::id());  // idnow refers to this thread

std::threadt(f);                 // launch a threadand call it t
id = t.get_id();                  // idnow refers to t's id
assert(id != std::this_thread::get_id());

this_thread Namespace

Note the use of thethis_thread namespace to disambiguate when you are requesting the id for thecurrent thread, vs the id of a child thread. The get_id name for this actionremains the same in the interest of reducing the conceptual footprint of theinterface. This design also applies to the cancellation_requested function:

std::threadmy_child_thread(f);
typedef std::thread::id ID:

IDmy_id std::this_thread::get_id();  // Thecurrent thread's id
ID your_id my_child_thread.get_id();  //The child   thread's id

boolhave_i_been_canceled = std::this_thread::cancellation_requested();  // Current thread's cancellationstatus
bool have_you_been_canceled = my_child_thread.cancellation_requested();  // Child  thread's cancellation status

The this_threadnamespace also contains a few other functions that operate on, or query thecurrent thread of execution.

Canceling threads

A joinablestd::thread can be cooperativelycanceled. When a thread cancels, all it does is throw an exception of typethread_canceled. Thus a canceled thread can cancel its cancel simply bycatching (and not re-throwing) thread_canceled. One thread can request thatanother thread throw a thread_canceled exception with this syntax:

std::threadt(f);
t.request_cancellation();  // requestthat the thread referred to by t cancel itself

To actually respondto a request to cancel, a thread must execute a cancelation point. A thread cancall cancellation_point() in order to turn any code into a cancellation point.

std::this_thread::cancellation_point();  // Will throw a thread_canceled exceptionif
                                        // and only if exceptions are enabled, and if
                                        // someone has called t.request_cancellation() where t refers
                                        // to this thread

Note that the mainthread can not be canceled (by another thread) because cancellation by anotherthread can only be done through a std::thread and there is no way to create astd::thread which refers to the main thread.

The list ofrecommended cancellation points is:

  • void     std::this_thread::cancellation_point()
  • template <class     ElapsedTime> void std::this_thread::sleep(const ElapsedTime&     rel_time)
  • void std::thread::join()
  • void     std::condition<Lock>::wait(Lock&)
  • template<class     Predicate> void std::condition<Lock>::wait(Lock&, Predicate)
  • bool     std::condition<Lock>::timed_wait(Lock&, const utc_time&)
  • template<class     Predicate> bool std::condition<Lock>::timed_wait(Lock&, const     utc_time&, Predicate)

A thread can disablecancellations for itself, even if it does execute a cancellation point such ascancellation_point. This is done with the disable_cancellation class. Theconstruction of this class has the effect of disabling cancellations for thelifetime of the object. When the object destructs, cancellation isautomatically reverted to its previous state (typically re-enabled).

{
std::this_thread::disable_cancellation _;
...
// cancellation can not happenhere
std::this_thread::cancellation_point(); // guaranteed to have no effect
...
}  // cancellation enabledhere
std::this_thread::cancellation_point(); // Will throw if someone has requested a request_cancellation()

Because cancellationis disabled with a class object, the cancellation is guaranteed to be correctlyenabled whether the scope is left normally, or by an exception (whether or notthat exception is thread_canceled. Note: within this document, a convention isused that if the object name is "_", that name is not used anywhere.

Cancellation is notdisabled during stack unwinding. Destructors must be cancellation safe whetherthey are being executed due to a thread_canceled exception, or any otherexception. Thus automatically disabling cancellation when a thread_canceled isthrown is redundant. And there is at least one corner case where it causes illeffects.

disable_cancellationscopes can be nested. That is, outer code can disable cancellation, and thencall other functions, without fear that those functions will disable, andsubsequently enable cancellation prematurely.

voidfoo()
{
   std::this_thread::disable_cancellation _;
    // cancellation disabledhere
    ...
}   // cancellation enabled only if itwas enabled upon entering foo

voidbar()
{
   std::this_thread::disable_cancellation _;
    // cancellation disabledhere
    foo();
    // cancellation still disabled here
}  // cancellation enabled only if it wasenabled upon entering bar

If the main threadcalls this_thread::cancellation_point() or constructs an object of typethis_thread::disable_cancellation, there is no effect. The main thread can notbe canceled. Having these functions silently ignore the main thread allowslibrary code to use this functionality without worry of whether it is beingexecuted in the main thread or not.

One can requestpermission from a disable_cancellation object to temporarily re-enablecancellation inside the scope. This requires a non-const disable_cancellationobject:

voidfoo()
{
   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

Therestore_cancellation constructor simply reverts to the cancellation state thatwas in effect with the referenced disable_cancellation object was constructed.Thus restore_cancellation doesn't actually enable cancellations unless thereferenced disable_cancellation was not constructed within the scope of anotherdisable_cancellation. Thus when a function says:

voidbar()
{
   std::this_thread::disable_cancellation _;
    foo();
}

then it is notpossible for code within the function foo to enable cancellation (even if ittries to as with this example). To enable cancellation in a called function,bar would have to communicate the name of its disable_cancellation object tothat function.

This design has beenset up to provide flexibility for disabling and enabling cancellation, yetprevent accidental enabling when calling unknown code, and to preventaccidentally not re-enabling when exceptions propagate through a stack frame.

Destructing a thread

Every thread musteither be joined or detached within its lifetime. To support cooperativecancellation, the thread destructor must be prepared to deal with threads whichhave neither been joined or detached. Consider for example a cancelable threadthat owns two child threads:

std::threadt1(f1);
std::thread t2(f2);
t1.join();
t2.join();  // what does t2.~thread() doif t1 throws thread_cancelled?

Upon cancellation ofthis thread (t1.join() is a cancellation point), a thread_canceled exceptionpropagates through the stack frame, destructing t2 before it has had a chanceto join. If t2 simply detaches, then t2 may run for an arbitrarily long time, andconsume arbitrarily large resources. This may result in the cancellationrequest of the parent thread effectively not being honored. Thus when a threadis destructed, if it is joinable then it is first canceled, and then detached.This allows the parent (canceling) thread to continue to cancel withoutblocking, and yet notify all of its child threads of the cancellation request.

If semantics otherthan request_cancellation(); detach(); are desired for a thread destructor,this is easy to arrange for with a threadmanager object. Such an object would be a scope guard for a thread whichpoints to the desired functionality upon destruction. Smart pointers withpolicy destructors are easily and efficiently employed as scope guards.

Threading cooperatively

Namespacethis_thread has two functions for yielding processor control to another thread:

voidyield();
template <class ElapsedTime> void sleep(const ElapsedTime& rel_time);

A thread can callthese functions to control its own yielding of the processor. There is no wayto request another thread to yield or sleep.

Note: See N2328for more details on handling time durations.

Environment

There is one staticmember function of thread which yields a measure of the number of threads whichcould possibly execute concurrently:

unsignedn = std::thread::hardware_concurrency();

This can come inhandy in a variety of situations such as sizing a thread pool.

Mutex

Mutex Rationale and Examples

Below is shown thebasic operation of a mutex. Normally one will want to lock and unlock mutexesusing scoped_lock<mutex> or unique_lock<mutex>. However lock,try_lock and unlock member functions are available in the mutex typesthemselves to provide flexibility to the client.

#include<mutex>

std::mutexm;

voidfoo()
{
    m.lock();
    // do work
    m.unlock();
}

Mutex concepts

Boost separatesmutex concepts out into:

  1. Mutex
  1. TryMutex
  1. TimedMutex

Each of these threeconcepts have both recursive and non-recursive counterparts for a total of 6concepts.

  1. Mutex
  1. RecursiveMutex
  1. TryMutex
  1. RecursiveTryMutex
  1. TimedMutex
  1. RecursiveTimedMutex

Because ofanticipated 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 thatall mutex concepts can support the TryMutex concept without extra expense.Therefore the TryMutex concept has been eliminated and folded into the Mutexconcept:

  1. Mutex
  2. RecursiveMutex
  3. TimedMutex
  4. RecursiveTimedMutex

It is shown laterthat the TryMutex concept is a necessary requirement for fundamental genericlock algorithms such as std::lock(L1&, L2&, L3&...) and is thus agood idea to include as a fundamental requirement for all mutex types (highbenefit, zero cost).

Time Issues

Most of thetime-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 oneexception to this policy is the timed_wait member of the condition variable. Inthis case spurious wake ups are expected, and when this happens, without timingagainst a specific point in time, it is difficult to know whether you've wokenup for spurious reasons or because of a time out, and if for spurious reasonshow much longer you need to wait for. Therefore timed_wait on conditionvariables alone is specified in terms of a specific point in time. Every efforthas been made to anticipate the TR2 date_time support and make the standardinterface compatible with that. See N2328for details.

Examples

std::timed_mutexmut;
std::basic_condition<std::unique_lock<std::mutex>> cv;

voidfoo()
{
    std::unique_lock<std::mutex>lk(mut);
    // Wait for 2 seconds on a conditionvariable
    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 timeout
    }
}

voidbar()
{
    // Wait for 1/10 of a second on amutex
    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 classtemplates which are templated on the mutex type. The locks thus become far morereusable. They can be instantiated with any standard or user-defined mutex whichmeets the mutex requirements.

std::scoped_lock<std::mutex> lock1(m1);  // ok
...
std::scoped_lock<std::timed_mutex>lock2(m2);  // alsook
...
std::scoped_lock<my_mutex>lock3(m3);  // also ok

There are two lockclass templates:

  1. template <class Mutex>     scoped_lock
  1. template <class Mutex>     unique_lock

The purpose ofscoped_lock is to serve the common use case with as much efficiency aspossible. Unlike the boost scoped_lock, this scoped_lock always owns itsreferenced mutex. There need be no internal flag indicating ownership. Thescoped_lock destructor does not need to perform a test to see if it shouldunlock the mutex: it unconditionally unlocks the mutex. Thus there is no branchwhich might stall a processor pipeline just to unlock the mutex.

Using a scoped_lockalso easily signals intent: The referenced mutex is locked and unlockedstrictly within the containing scope. There is no need for the reader of thecode to search for places where mutex ownership might be transferred out of thecurrent scope.

voidfoo()
{
    std::scoped_lock<std::mutex>_(mut);
    // do protected work
    // ...
}   // mut unlocked

It is not possibleto have a scoped_lock that does not refer to a mutex. And it is not possiblefor that referenced mutex to not be locked by the scoped_lock. The only way tolock the mutex is with the scoped_lock constructor, and the only way to unlock itis with the scoped_lock destructor. This is far more restrictive than the boostscoped_lock, but slightly more efficient as well. Because of the prevalence ofthe scoped locking pattern, it is felt by the authors that a maximallyefficient lock dedicated to this use case is justified.

Because there existuse cases which require more flexibility than a strict scoped style lockingpattern, unique_lock is introduced. Unlike scoped_lock, unique_lock may or maynot reference a mutex, and if it does, may or may not own the locked state of thatmutex. This is much more like the semantics of the boost scoped_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 canbe put into containers and returned from factory functions.

template<class L1, class L2>
int
try_lock(L1& lock1, L2& lock2)
{
    unique_lock<L1>ul(lock1, try_to_lock);
    if (ul.owns())
    {
        if (lock2.try_lock())
        {
            ul.release();
            return -1;
        }
        return 1;
    }
    return 0;
}

In the exampleabove, unique_lock serves to provide exception safety, unlocking lock1 if theattempt to lock lock2 throws an exception. However, because strict scopedlocking isn't desired in this use case, the unique_lock is asked to release itslock ownership if both lock1 and lock2 are successfully locked.

Also note in theabove example that L1 and L2 may also be unique_lock types. Because of thegenerality of the templated locks (as opposed to being available only as nestedtypes of a mutex), the try_lock algorithm can easily and seamlessly create aunique_lock<unique_lock<Mutex>> type (ul in the example if L1 is aunique_lock<Mutex>).

Finally note a fewsyntactic differences between boost scoped_lock and unique_lock which lead toimproved 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, TR2may have a new lock type that does not model exclusive ownership as scoped_lockand unique_lock do, but rather models shared ownership. A reasonable name forsuch a lock might be shared_lock.

  • template <class Mutex>     scoped_lock; // Scoped, exclusive ownership
  • template <class Mutex>     unique_lock; // Exclusive ownership
  • template <class Mutex>     shared_lock; // Shared ownership

We feel that theabove is an appropriate naming convention for the various lock types.

Generic Locking Algorithm Rationale and Examples

Consider a userwritten class which contains a data member mutex which controls access to theobject:

classRecord
{
    mutable std::mutex mut;
    ...
public:
    ...
};

Now consider writingthe assignment operator for this class:

Record&
Record::operator=(const Record& r)
{
    if (this != &r)
    {
        std::scoped_lock<std::mutex>this_lock(mut);
       std::scoped_lock<std::mutex> that_lock(r.mut);
        // Both source and destinationare locked
        // Safe to assign
        // ...
    }
    return *this;
}

Unfortunately theabove 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 threadexecutes r2 = r1, then it is possible to deadlock. For example:

ThreadA                Thread B
lock r1.mut             lockr2.mut
block on r2.mut         block on r1.mut

To address thissituation a generic locking algorithm is provided which locks an arbitrarynumber of locks at the same time while avoiding deadlock:

template<class L1, class L2, class ...L3> void lock(L1&, L2&,L3&...);

Now the assignmentoperator can easily be written to avoid deadlock:

Record&
Record::operator=(const Record& r)
{
    if (this != &r)
    {
        std::unique_lock<std::mutex>this_lock(mut, std::defer_lock);
       std::unique_lock<std::mutex> that_lock(r.mut,std::defer_lock);
        std::lock(this_lock,that_lock);
        // Both source and destinationare locked
        // Safe to assign
        // ...
    }
    return *this;
}

unique_lock is nowrequired instead of scoped_lock as one can not defer the locking of the mutexwithin a scoped_lock. Note too that the locks locked with std::lock do not needto be the same type. So if we have read/write locking in the future this mightlook like:

Record&
Record::operator=(const Record& r)
{
    if (this != &r)
    {
        std::unique_lock<std::tr2::rw_mutex>      this_lock(mut,std::defer_lock);
        std::tr2::shared_lock<std::tr2::rw_mutex> that_lock(r.mut,std::defer_lock);
        std::lock(this_lock,that_lock);
        // Both source and destinationare locked
        // Safe to assign
        // ...
    }
    return *this;
}

In the above examplethis is write-locked and r is read-locked, all done in a deadlock-safe manner.

Condition Variables

Condition variablesare a inter-thread notification mechanism which work closely with mutexes andlocks. 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 thequeue have items). When the predicate is false, the thread will wait on thecondition variable using the still locked mutex as an argument to the waitfunction. The locked mutex assures that no other thread can change theprotected data while the current thread-of-execution is in the process ofblocking (waiting) for that data to be updated. Once the waiting thread isblocked, the system unlocks the mutex so that another thread can lock themutex, update the protected data, unlock the mutex, and signal the conditionvariable to wake one or more threads to process the protected data.

Condition Rationale and Examples

std::mutexmut;
std::condition_ulm cv;
std::queue<int> data;

voidthread1()  // consumer
{
    while (true)
    {
        int d;
        {
       std::unique_lock<std::mutex> lk(mut);  // Protect data
        while (data.empty())                   // Is there data toprocess?
            cv.wait(lk);                       // Sleep and releasedata lock
        d = data.front();                      // Remove data fromnon-empty
        data.pop();                            //    queue with mutex locked
        }
        process(d);                            // Process datawith mutex unlocked
    }
}

voidthread2()  // producer

    while (true)
    {
        int d = get_more_data();               // Produce data with mutexunlocked
       std::scoped_lock<std::mutex> _(mut);  // Protect data
        data.push(d);                          // get data and pushit into queue
        if (data.size() == 1)
            cv.notify_one();                   // Notify thread1 that dataqueue has become non-empty
    }
}

The example abovedemonstrates basic condition usage. The condition type: condition_ulm is atypedef for condition<unique_lock<mutex>> (the ulm is an acronym for unique_lock<mutex>). thread1 acts as a consumer, waitinguntil there is data in the queue to process. The queue is checked, and data isremoved from the queue under the protection of a std::mutex which is lockedwith a std::unique_lock. While thread1 waits for the empty queue to have datapushed into it, the system unlocks the mutex.

thread2 in the aboveexample supplies data to the queue. As it is accesses the shared data queue itprotects it with the std::mutex. When thread2 detects that the queue hastransitioned from empty to one element, it signals thread1 via the conditionvariable. If thread1 isn't blocked on the condition variable at this time, thenotification is harmlessly ignored.

Both thread1 andthread2 do as much processing as possible with the std::mutex unlocked, thusincreasing overall throughput.

Condition Variable Flexibility

A template classcondition<Lock> is supplied, where the only requirements on Lock are thatit support lock() and unlock() member functions. Lock could be any of thestandard mutexes or locks, or any user defined mutex or lock (as long as theymeet the lock/unlock requirements.

Assuming that TR2brings read/write mutexes and shared locks, they will be usable with thisstd::condition. Being able to wait with a read/write mutex, locked either forreading or writing, goes significantly beyond Posix capabilities and Boostcapabilities (though Windows Vista has this capability).

std::tr2::rw_mutexmut;
std::condition<std::tr2::shared_lock<std::tr2::rw_mutex>> cv;

voidfoo()
{
   std::tr2::shared_lock<std::tr2::rw_mutex>read_lock(mut);
    while(there_is_nothing_to_read())
        cv.wait(read_lock);
    ...
}

This pattern mightallow a single producer, needing a write lock, to signal many consumers whichneed only read locks to "consume", which finally might signal asingle clean up thread needing a write lock to dispose of the data.

Proposed wording

 

Chapter 30   Multi-threading library

 

This clausedescribes components that C++ programs may use to create and managemulti-threaded programs.

The followingsubclauses describe components to create and manage threads-of-execution,perform mutual exclusion and locking, and communicate betweenthreads-of-execution.

 
 

Subclause

 
 
 

Header(s)

 
 
 

Threads

 
 
 

<thread>

 
 
 

Mutexs  and locks

 
 
 

<mutex>

 
 
 

Condition  variables

 
 
 

<condition>

 

Some functionsdescribed in this clause are specified to throw exceptions of type system_error([syserr.syserr]). The error_category ([syserr.errcat.overview]) of theerror_code reported by such exceptions code() member function isimplementation-defined. [Note: Thecategory is typically native_category ([syserr.errcat.overview]) since theseerror codes usually originate from the underlying operating system applicationprogram interface (API). -- end note]

Threads

<thread> synopsis

namespacestd {

classthread_canceled;
class thread;

voidswap(thread&  x, thread&  y);
void swap(thread&& x, thread& y);
void swap(thread&  x,thread&& y);

classthread::id;
bool operator==(const thread::id& x, const thread::id& y);
bool operator!=(const thread::id& x, const thread::id& y);

template<classcharT, class traits>
basic_ostream<charT, traits>&
operator<< (basic_ostream<charT, traits>&& out, constthread::id& id);

namespacethis_thread
{
    classdisable_cancellation;
    class restore_cancellation;

voidcancellation_point();
    boolcancellation_enabled();
    boolcancellation_requested();
   
    thread::id get_id();
   
    void yield();
    template <class ElapsedTime>
        void sleep(const ElapsedTime&rel_t);

}  // this_thread

structonce_flag
{
    constexpr once_flag();

once_flag(constonce_flag&) = delete;
    once_flag& operator=(constonce_flag&) = delete;
};

template<typenameCallable, typename Args...>
void call_once(once_flag& flag, Callable func, Args... args);

}  // std

Class thread_canceled

An exception classthread_canceled is thrown to execute a cancellation request. [Note: thread_canceled does not derive fromexception to avoid being caught by accident. --endnote]

classthread_canceled
{
public:
    thread_canceled();
    virtual~thread_canceled();
    virtual const char* what()const;
};

thread_canceled()

Effects: Constructs an object of typethread_canceled.

Throws: Nothing.

~thread_canceled()

Effects: Destructs an object of typethread_canceled.

Throws: Nothing.

constchar* what()

Returns: An implementation-defined NTBS.

Throws: Nothing.

Remarks: The message may be a null-terminatedmultibyte string ([multibyte.strings]), suitable for conversion and display asa wstring ([string.classes], [locale.codecvt]). The return value remains validuntil the exception object from which it is obtained is destroyed or anon-const member function of the exception object is called.

Class thread

An object of classthread launches a new thread-of-execution, and provides mechanisms for thecurrent thread-of-execution to wait for completion of the launched thread,request cooperative cancellation of the launched thread, and perform otheroperations to manage and query the thread's state.

classthread
{
public:
    thread();
    template <class F> explicitthread(F f);
    ~thread();

thread(constthread&) = delete;
    thread& operator=(constthread&) = delete;

thread(thread&&);
    thread&operator=(thread&&);

voidswap(thread&&);

voidrequest_cancellation();
    bool cancellation_requested() const;

booljoinable() const;
    void join();
    template <classElapsedTime>
        bool timed_join(constElapsedTime& rel_t);
    void detach();

classid
    {
    public:
        id();
        friend bool operator==(const id&x, const id& y);
        friend bool operator!=(constid& x, const id& y);
        friend bool operator<(constid& x, const id& y);
        friend bool operator<=(constid& x, const id& y);
        friend bool operator>(constid& x, const id& y);
        friend bool operator>=(constid& x, const id& y);
    };

idget_id() const;

typedefimplementation-definednative_handle_type;
    native_handle_type native_handle();

staticunsigned hardware_concurrency();
};

Class thread andclass 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 thatrefers to not any thread. This identitycompares equal to other non-joinable threads, and compares not equal to allother joinable threads.

Throws: Nothing.

template<class F> explicit thread(F f)

Requires: If f is an lvalue, F must beCopyConstructible. If f is an rvalue, F must only be MoveConstructible.

Effects: Constructs an object of type threadand executes the functor fasynchronously as a new thread-of-execution. F is a functor which takes noargument. Any return value from the functor is ignored. If f terminates with anuncaught exception of type thread_canceled, or of type publicly derived fromthread_canceled, then the effect shall be as if f returned normally. If fterminates with an uncaught exception of any other type, std::terminate() shallbe called.

Postconditions:

get_id()!= thread::id()
&& joinable() == true

Forthe newly created thread-of-execution, this_thread::cancellation_enabled() istrue. [Note: cancellation is enabledupon thread creation. -- end note]

*thisrepresents the newly started thread-of-execution.

Throws: system_error if unable to start thisthread.

~thread()

Effects: If joinable() thenrequest_cancellation() followed by detach(), otherwise no effects.

Throws: Nothing.

thread(thread&&x)

Effects: Constructs an object of type threadfrom 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 tothe start ofconstruction.

Throws: Nothing.

thread&operator=(thread&& x)

Effects: If this currently refers to ajoinable thread, calls request_cancellation() and detach(). Then assigns thestate of x to *this and 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 valueof x.get_id() prior to the assignment.

Throws: Nothing.

voidswap(thread&& x)

Effects: Swaps the state of *this and x.

Throws: Nothing.

voidrequest_cancellation()

Preconditions: joinable() is true.

Postcondition: cancellation_requested() istrue.

Throws: Nothing.

boolcancellation_requested() const

Preconditions: joinable() is true.

Returns: For the thread-of-executionrepresented by *this, this_thread::cancellation_requested().

Throws: Nothing.

booljoinable() const

Returns: get_id() != id().

Throws: Nothing.

voidjoin()

Preconditions: joinable() is true.

Effects: The current thread-of-executionblocks until the thread-of-execution represented by *this completes.

Postconditions: After a normal return ofjoin(), joinable() is false. An exceptional return will indicate that thethread-of-execution represented by *this has received a request to cancel. Insuch an event, the thread-of-execution represented by *this remains unaffected.

Throws: If, for the thread-of-executionrepresented by *this, this_thread::cancellation_requested() becomes true duringthe call to join(), throws thread_canceled.

Remarks: This function is a cancellation pointfor the current thread-of-execution. [Note:The main thread can not be canceled even at a cancellation point. --end note]

template<class ElapsedTime>
    bool timed_join(constElapsedTime& rel_t)

Requires: ElapsedTime shall be explicitlyconvertible to nanoseconds.

Preconditions: joinable() is true.

Effects: The current thread-of-executionblocks until the the thread-of-execution represented by *this completes, oruntil the indicated time duration expires.

Postconditions: If timed_join returns true,joinable() shall be false. An exceptional return will indicate that the currentthread-of-execution has received a request to cancel. In such an event, the thethread-of-execution represented by *this remains unaffected.

Returns: true if the thread-of-executionrepresented by *this joined, otherwise false.

Throws: If, for the thread-of-executionrepresented by *this, this_thread::cancellation_requested() becomes true duringthe call to join(), throws thread_canceled.

Remarks: This function is a cancellation pointfor the current thread-of-execution. [Note:The main thread can not be canceled even at a cancellation point. --end note]

voiddetach()

Preconditions: joinable() is true.

Effects: The thread-of-execution representedby *this continues execution. When the thread-of-execution represented by *thisends 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 typethread::id which compares equal to other default constructed thread::idobjects.

Throws: Nothing.

booloperator==(const id& x, constid& y)

Returns: If xand y both represent not any thread, then returns true. Otherwiseif x and yrepresent the same thread-of-execution, then returns true. Otherwise returnsfalse.

Throws: Nothing.

booloperator!=(const id& x, constid& y)

Returns: !(x== y)

Throws: Nothing.

booloperator<(const thread_id& x, const thread_id& y)

Returns: Provides an ordering for all objectsof type thread_id, such that objects of type thread_id can be used as a key inAssociate Containers. For two objects of type thread_id, x and y, if x == yreturns true, both x < y and y < x shall return false. Otherwise,precisely one of x < y and y < x shall return true.

Throws: Nothing.

booloperator<=(const thread_id& x, const thread_id& y)

Returns: !(y < x)

Throws: Nothing.

booloperator>(const thread_id& x, const thread_id& y)

Returns: y < x

Throws: Nothing.

booloperator>=(const thread_id& x, const thread_id& y)

Returns: !(x < y)

Throws: Nothing.

idget_id() const

Returns: A thread::id which refers to thethread-of-execution represented by *this. If this thread is not joinable()returns a default constructed id.

Throws: Nothing.

native_handle_typenative_handle()

Returns: An implementation defined typerepresenting the underlying OS thread handle.

Throws: Nothing.

unsignedhardware_concurrency()

Returns: The number of threads that canreasonably 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 areturn value of 1 is recommended, but not required.

Throws: Nothing.

voidswap(thread&  x, thread&  y);
void swap(thread&& x, thread& y);
void swap(thread&  x,thread&& y);

Effects: x.swap(y).

template<classcharT, class traits>
basic_ostream<charT, traits>&
operator<< (basic_ostream<charT, traits>&& out, constthread::id& id);

Effects: Inserts an unspecified textrepresentation of the thread::id into the stream out.

Returns: out.

namespacethis_thread
{

classdisable_cancellation
    {
    public:
       disable_cancellation();
        ~disable_cancellation();

disable_cancellation(constdisable_cancellation&) = delete;
        disable_cancellation&operator=(const disable_cancellation&) = delete;
    };
}  // this_thread

disable_cancellation()

Effects: Constructs an object of typedisable_cancellation. The construction has the effect of disabling requests tocancel the current thread-of-execution from other threads during the lifetimeof this object (except as modified by restore_cancellation). When acancellation point is executed within the lifetime of this object, a request tocancel has no affect (except as modified by restore_cancellation). Theconstructor also notes the current cancellation state so that it can berestored at the time this object is destructed.

Throws: Nothing.

Postconditions:this_thread::cancellation_enabled() returns false.

Remarks: This function has no effect ifexecuted from the main thread.

~disable_cancellation()

Effects: Restores the enable-cancellationstate to the same as it was when this disable_cancellation was constructed.

Throws: Nothing.

Remarks: This function has no effect ifexecuted from the main thread.

namespacethis_thread
{
    class restore_cancellation
    {
    public:
        explicitrestore_cancellation(disable_cancellation&);
        ~restore_cancellation();

restore_cancellation(constrestore_cancellation&) = delete;
        restore_cancellation&operator=(const restore_cancellation&) = delete;
    };
}  // this_thread

restore_cancellation(disable_cancellation&d)

Effects: Constructs an object of typerestore_cancellation. The enable-cancellation state is set to the same statethat would be observed immediately after d.~disable_cancellation().

Note: The enable-cancellation may notnecessarily be enabled if this construction happens within nested scopes ofdisable_cancellation objects.

Throws: Nothing.

Remarks: This function has no effect ifexecuted from the main thread.

~restore_cancellation()

Effects: Disables cancellation.

Postconditions:this_thread::cancellation_enabled() returns false.

Throws: Nothing.

Remarks: This function has no effect ifexecuted from the main thread.

namespacethis_thread
{
    void cancellation_point();
    boolcancellation_enabled();
    bool cancellation_requested();
   
    thread::id get_id();
   
    void yield();
    template <classElapsedTime>
        void sleep(const ElapsedTime&rel_t);

}  // this_thread

voidcancellation_point()

Effects: If cancellation_enabled() &&cancellation_requested() then throws an exception of type thread_canceled, elsethere is no effect.

Postconditions: If a thread_canceled isthrown, then cancellation_requested() shall be false.

Throws: thread_canceled.

boolcancellation_enabled()

Returns: If this is the main thread, returnsfalse. Otherwise returns true unless a disable_cancellation object has beenconstructed (and not destructed) and which has not been reverted with arestore_cancellation object.

Throws: Nothing.

boolcancellation_requested()

Returns: true if request_cancellation() hasbeen called on this thread and the thread has not yet executed a cancellationpoint with cancellation enabled.

Throws: Nothing.

thread::idthis_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.

voidyield()

Effects: Offers the operating system thechance to schedule another thread.

Throws: Nothing.

template<class ElapsedTime>
    void sleep(const ElapsedTime&rel_t)

Requires: ElapsedTime shall be explicitlyconvertible to nanoseconds.

Effects: The current thread-of-executionblocks for at least the amount of time specified, unless it receives a requestto cancel.

Throws: Nothing.

Remarks: This function is a cancellationpoint.

struct once_flag

Objectsof class once_flag are opaque data structures that allow call_once toinitialize data without causing a data race or deadlock.

constexpronce_flag();

Effects: Constructs a object of typeonce_flag.

Postcondition: Internal state is set toindicate to an invocation of call_once with this once_flag as its initialargument that no function has been called.

non-member function call_once

template<typenameCallable, typename Args...>
void call_once(once_flag& flag, Callable func, Args... args);

Requires: If the Callable argument func is anlvalue, F is CopyConstructible. Otherwise, func is an rvalue, and F isMoveConstructible. Copying or moving (as appropriate) shall have no sideeffects, and the effect of calling the copy shall be equivalent to calling theoriginal.

Effects: The argument func (or a copy thereof)is called exactly once for the once_flag object specified by flag, as-if byinvoking func(args), even if call_once is called multiple times for the sameonce_flag object. If multiple calls to call_once with the same once_flag objectoccur in separate threads-of-execution, only one thread shall call func, andnone of the threads shall proceed until the call to func has completed. If theinvocation of func results in an exception being thrown, the exception ispropagated to the caller and the effects are as-if this invocationof call_once did not occur.

Throws: system_error or any exceptionpropagated from func.

Thread safety: Access to the same once_flagobject by calls to call_once from different threads-of-execution shall notresult in a data race or deadlock.

[Examples:

std::once_flagflag;

voidinit();

voidf()
{
    std::call_once(flag,init);
}

structinitializer
{
    void operator()();
};

voidg()
{
    static std::once_flagflag2;
   std::call_once(flag2,initializer());
}

-- end example]

Mutexs and locks

<mutex> synopsis

namespacestd {

structstatic_mutex;
struct mutex;
struct recursive_mutex;
struct timed_mutex;
struct recursive_timed_mutex;

structdefer_lock_type;
struct try_lock_type;
struct accept_ownership_type;

externdefer_lock_type      defer_lock;
extern try_lock_type        try_to_lock;
extern accept_ownership_type accept_ownership;

classlock_error;

template<class Mutex> class scoped_lock;
template <class Mutex> class unique_lock;

template<class Mutex> void swap(unique_lock<Mutex>&  x, unique_lock<Mutex>&  y);
template <class Mutex> void swap(unique_lock<Mutex>&& x,unique_lock<Mutex>& y);
template <class Mutex> void swap(unique_lock<Mutex>&  x, unique_lock<Mutex>&& 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 mutextypes enforce mutual exclusion between threads-of-execution by limitingownership of a mutex object to a single thread-of-execution. Athread-of-execution gets ownership of a mutex object by calling lock() andrelinquishes ownership by calling unlock(). Ownership can not be transferredfrom one thread-of-execution to another. The same thread-of-execution thatcalls lock() for a mutex object must call unlock() for the object. Mutexes canbe either recursive or non-recursive. The syntax is the same for both recursiveand non-recursive mutexes, but the semantics for the member functions differsas described below.

Each mutex typeshall be default constructible and destructible. If the default construction ofthe Mutex type fails, an exception of type system_error shall be thrown. Thedestructor of the Mutex type shall not throw an exception. Mutex types areneither copyable nor movable. Each mutex type shall have the following memberfunctions:

voidlock();

Precondition: For non-recursive mutexes thecurrent thread-of-execution shall not own the mutex.

Effects: The current thread-of-execution willblock until the mutex is not owned by another thread-of-execution. Uponsuccessful completion, the current thread-of-execution owns the mutex.

Throws: system_error.

Thread safety: Calls from differentthreads-of-execution to lock, try_lock, and unlock functions on an object of amutex type shall not result in data races or deadlocks.

booltry_lock();

Precondition: For non-recursive mutexes thecurrent thread-of-execution shall not own the mutex.

Effects: If ownership can be obtained withoutblocking, 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 differentthreads-of-execution to lock, try_lock, and unlock functions on an object of amutex type shall not result in data races or deadlocks.

Throws: Nothing.

voidunlock();

Precondition: The current thread-of-executionshall own the mutex.

Effects: For a non-recursive mutex ownershipis released. For a recursive mutex unlock() must be called the same number oftimes which the mutex was locked (via either lock() or try_lock() or by anyother locking function) before ownership is released.

Thread safety: Calls from differentthreads-of-execution to lock, and try_lock functions on an object of a mutextype shall not result in data races or deadlocks.

Throws: Nothing.

If and only if themutex type is internally represented by a single data structure which can bepassed to operating system specific interfaces, then there shall be a nestedimplementation-defined typedef native_handle_type that is an alias to thisnative type if it is copyable, otherwise if the native type is not copyable, isa pointer to this native type. The implementation shall document whether or notthe native_handle_type typedef is present.

If the nestedtypedef native_handle_type exists, then there also shall be a member functionnative_handle() which returns a handle to this internal data structure. [Example:

classmutex
{
    pthread_mutex_tm;
public:
    typedef pthread_mutex_t*native_handle_type;
    native_handle_type native_handle(){return &m;}
    ...
};

--end example]

If there is nosingle operating system specific data structure which implements the mutextype, then neither the nested type native_handle_type nor the member functionnative_handle() shall not be present. [Example:if a recursive_mutex is implemented with both a pthread_mutex_t and a separatelock count, then there will be no native_handle_type. --end example]

Implementations maysupply additional implementation defined constructors which allow furthercustomization as afforded by the implementation or its environment.

Class static_mutex

The class static_mutex is based on a new languagefeature constexpr which is not yet in the working draft, nor do we have fieldexperience with it. Should this language feature fail to deliver the staticinitialization behavior desired, we recommend removing static_mutex from theworking paper.

namespacestd {

structstatic_mutex
{
public:
    constexpr static_mutex();
    ~static_mutex();

static_mutex(conststatic_mutex&) = delete;
    static_mutex& operator=(conststatic_mutex&) = delete;

voidlock();
    bool try_lock();
    void unlock();

typedefunspecified native_handle_type;  //conditionally present.  example:pthread_mutex_t*
    native_handle_type native_handle();      // conditionally present
};

}  // std

The classstatic_mutex is a non-recursive mutex. It shall be a standard-layout type([?]), and does not require dynamic initialization. The default constructor, ifdynamically initialized, shall not throw an exception.

Class mutex

namespacestd {

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

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

voidlock();
    bool try_lock();
    void unlock();

typedefunspecified native_handle_type;  //conditionally present.  example:pthread_mutex_t*
    native_handle_type native_handle();      // conditionally present
};

}  // std

The class mutex is anon-recursive mutex which satisfies all of the Mutex requirements. It shall bea standard-layout type ([?]).

Class recursive_mutex

namespacestd {

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

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

voidlock();
    bool try_lock();
    void unlock();

typedefunspecified native_handle_type;  //conditionally present.  example:pthread_mutex_t*
    native_handle_type native_handle();      // conditionally present
};

}  // std

The classrecursive_mutex shall be a recursive mutex which satisfies all of the Mutexrequirements. It shall be a standard-layout type ([?]).

Timed Mutexes

Types that meet therequirements of the Timed Mutex concept also meet the requirements of the Mutexconcept and add a single member function:

template<class ElapsedTime>
    bool timed_lock(constElapsedTime& rel_time);

Precondition: For non-recursive mutexes thecurrent thread-of-execution shall not own the mutex. The type ElapsedTime shallbe explicitly convertible to nanoseconds.

Effects: The function attempts to obtainownership of the mutex within the specified time. If the indicated time is lessthan or equal to 0, the function still attempts to obtain ownership withoutblocking (as if by calling try_lock()). If the function returns within thespecified time duration, it shall have obtained ownership.

Returns: true if ownership was obtained,otherwise false.

Thread safety: Calls to this member functionfrom different threads-of-execution shall not result in data races ordeadlocks.

Throws: Nothing.

Class timed_mutex

namespacestd {

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

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

voidlock();
    bool try_lock();
    template <classElapsedTime>
        bool timed_lock(constElapsedTime& rel_time);
    void unlock();

typedefunspecified native_handle_type;  //conditionally present.  example:pthread_mutex_t*
    native_handle_type native_handle();      // conditionally present
};

}  // std

The classtimed_mutex is a non-recursive mutex that satisfies all of the Timed Mutexrequirements. It shall be a standard-layout type ([?]).

Class recursive_timed_mutex

namespacestd {

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

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

voidlock();
    bool try_lock();
    template <classElapsedTime>
        bool timed_lock(constElapsedTime& rel_time);
    void unlock();

typedefunspecified native_handle_type;  //conditionally present.  example:pthread_mutex_t*
    native_handle_type native_handle();      // conditionally present
};

}  // std

The classrecursive_timed_mutex shall be a recursive mutex that satisfies all of theTimed Mutex requirements. It shall be a standard-layout type ([?]).

Locks

Locks are objectsthat hold a reference to a mutex and unlock the mutex during the lock'sdestruction (such as when leaving block scope). The locks do not manage thelifetime of the mutex they reference, but only the ownership status of thatmutex. [Note: Locks are intended to easethe burden of unlocking the mutex under both normal and exceptionalcircumstances. --end note]

Some locks may taketag types which describe what should be done with the mutex in the lock'sconstructor.

structdefer_lock_type       {};
struct try_lock_type        {};
struct accept_ownership_type {};

externdefer_lock_type      defer_lock;
extern try_lock_type        try_to_lock;
extern accept_ownership_type accept_ownership;

An exception classlock_error derives from exception and is used to indicate improper usage oflocks such as locking a mutex that the lock already owns, or unlocking a mutexthat the lock does not own.

classlock_error
    : publicstd::exception
{
public:
    virtual const char* what() constthrow();
};

Class scoped_lock

namespacestd {

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

explicitscoped_lock(mutex_type& m);
    scoped_lock(mutex_type& m,accept_ownership_type);
    ~scoped_lock();

scoped_lock(scoped_lockconst&) = delete;
    scoped_lock&operator=(scoped_lock const&) = delete;

constexprbool owns() const;
};

}  // std

scoped_lock is usedto control the ownership of a mutex within a single scope. An invariant of thescoped_lock object is that it maintains the ownership of the mutex throughoutthe scoped_lock's lifetime. Mutex ownership can not be deferred or transferredaway from the scoped_lock.

explicitscoped_lock(mutex_type& m);

Precondition: If mutex_type is not a recursivemutex, the current thread-of-execution does not own the mutex. The lifetime of m is greater than the lifetime of thescoped_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-executionhas ownership of the mutex m. Thelifetime of m is greater than thelifetime 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.

constexprbool owns() const;

Returns: true.

Throws: Nothing.

Class unique_lock

namespacestd {

template<class Mutex>
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_lockconst&) = delete;
    unique_lock&operator=(unique_lock const&) = delete;

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

voidlock();
    bool try_lock();
    template <classElapsedTime>
        bool timed_lock(constElapsedTime& rel_t);
    void unlock();

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

voidswap(unique_lock&& u);
    mutex_type* release();
};

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

}  // std

unique_lock is usedto control the ownership of a mutex within one or more scopes. Mutex ownershipcan 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 typeunique_lock.

Postcondition:

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

explicitunique_lock(mutex_type& m);

Precondition: If mutex_type is not a recursivemutex, the current thread-of-execution does not own the mutex. The lifetime of m is greater than the lifetime of theunique_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 recursivemutex, the current thread-of-execution does not own the mutex. The lifetime of m is greater than the lifetime of theunique_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 recursivemutex, then the current thread-of-execution does not own the mutex. Thelifetime of m is greater than thelifetime 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-executionhas ownership of the mutex m. Thelifetime of m is greater than thelifetime 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 thereferenced 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 thentransfers 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 possiblethat both this and u own the same mutex before the assignment. In this case,this will own the mutex after the assignment (and u will not), but the mutex'slock count will be decremented by one.

voidlock();

Effects: Calls lock() on the referenced mutex.

Postcondition: owns() == true.

Throws: lock_error, if on entry owns() istrue.

booltry_lock();

Effects: Calls try_lock() on the referencedmutex.

Returns: The result of the call to try_lock()on the referenced mutex.

Postcondition: owns() == The result of thecall to try_lock() on the referenced mutex.

Throws: lock_error, if on entry owns() istrue.

template<class ElapsedTime>
   bool timed_lock(const ElapsedTime&rel_t);

Effects: Calls timed_lock(rel_t) on thereferenced mutex.

Returns: The result of the call totimed_lock(rel_t) on the referenced mutex.

Postcondition: owns() == The result of thecall to timed_lock(rel_t) on the referenced mutex.

Throws: lock_error, if on entry owns() istrue.

voidunlock();

Effects: Calls void unlock() on the referencedmutex.

Postcondition: owns() == false.

Throws: lock_error, if on entry owns() isfalse.

boolowns() const;

Returns: true if this owns a lock on areferenced mutex, else false.

Throws: Nothing.

operatorunspecified-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, ornull if there is no referenced mutex.

Throws: Nothing.

voidswap(unique_lock&& u);

Effects: Swaps state with u.

Throws: Nothing.

mutex_type*release();

Returns: A pointer to the referenced mutex, ornull if there is no referenced mutex.

Postcondition:

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

Throws: Nothing.

template<class Mutex> void swap(unique_lock<Mutex>&  x, unique_lock<Mutex>&  y);
template <class Mutex> void swap(unique_lock<Mutex>&& x,unique_lock<Mutex>& y);
template <class Mutex> void swap(unique_lock<Mutex>&  x, unique_lock<Mutex>&& 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 mustsupply the following member functions with semantics corresponding to the Mutexconcept, except that try_lock is allowed to throw an exception. [Note: The unique_lock class template meetsthese requirements when suitable instantiated. --endnote]

booltry_lock();
void unlock();

Effects: The functions attempts to lock allarguments without blocking by calling try_lock() on each of them. If anyargument can not be locked, then all arguments which have already been lockedwill be unlocked. On return, either all arguments will be locked, or none ofthem will be locked. If an exception is thrown by a call to try_lock(), thereare no effects.

Returns: If all arguments were successfullylocked, returns -1. Otherwise returns a 0-based index value indicating whichargument failed to lock.

template<class L1, class L2, class ...L3> void lock(L1&, L2&,L3&...);

Requires: Each template parameter type mustsupply the following member functions with semantics corresponding to the Mutexconcept, except that try_lock is allowed to throw an exception [Note: The unique_lock class template meetsthese requirements when suitable instantiated. --endnote]

voidlock();
bool try_lock();
void unlock();

Effects: All arguments are locked with analgorithm that avoids deadlock. If an exception is thrown by a call to lock()or try_lock(), there are no effects.

Condition variables

<condition> synopsis

namespacestd {

template<class Lock> class condition;

typedefcondition<mutex>             condition_mtx;
typedef condition<unique_lock<mutex>> condition_ulm;

}  // std

Class template condition

An object of classtemplate condition is a synchronization primitive used to cause athread-of-execution to wait until notified by some other thread-of-executionthat some condition is met, or a UTC[(?)] time is reached.

The Lock type mustsupport member functions lock and unlock with the semantics of the mutexconcept. All of the standard mutex types meet this requirement. AdditionallyLock may provide an owns() signature returning bool as outlined for theunique_lock class template. If present, the condition class template will usethis member for error checking.

namespacestd {

template<class Lock>
class condition
{
public:
    typedef Lock lock_type;

condition();
    ~condition();

condition(constcondition&) = delete;
    condition& operator=(constcondition&) = delete;

voidnotify_one();
    void notify_all();
    void wait(lock_type&lock);
    template <classPredicate>
        void wait(lock_type& lock,Predicate pred);
    bool timed_wait(lock_type& lock,const utc_time& abs_time);
    template <classPredicate>
        bool timed_wait(lock_type&lock, const utc_time& abs_time, Predicate pred);
};

}  // std

condition();

Effects: Constructs an object of classcondition.

~condition();

Effects: Destroys the object.

Throws: Nothing.

voidnotify_one();

Effects: If any threads-of-execution areblocked waiting for *this, unblocks at least one those threads.

Thread safety: Calls to the wait, timed_wait,notify_one or notify_all member functions of the same condition object fromdifferent threads-of-execution shall not result in data races or deadlocks.

voidnotify_all();

Effects: Unblock all threads that are blockedwaiting for *this.

Thread safety: Calls to the wait, timed_wait,notify_one or notify_all member functions of the same condition object fromdifferent threads-of-execution shall not result in data races or deadlocks.

voidwait(lock_type& lock);

Precondition: lock is locked by the currentthread-of-execution. If lock_type supports recursive locking, the lock count isone. No other thread-of-execution is waiting on this condition object unlesslock is, or refers to, the same underlying mutex object.

Effects: Atomically blocks and releases thelock on lock. If the thread-of-execution is canceled while blocked, lock willbe locked as the thread_canceled exception propagates out. Thisthread-of-execution shall unblock when another thread issues a notification tothis blocked thread. The current thread-of-execution may unblock and returneven in the absence of a notification.

Postcondition: lock is locked by the currentthread-of-execution.

Throws: thread_canceled, system_error. Iflock_type has an owns() member function and lock.owns() returns false uponentry, a lock_error is thrown.

Thread safety: Calls to the wait, timed_wait,notify_one or notify_all member functions of the same condition object fromdifferent threads-of-execution shall not result in data races or deadlocks.

Remarks: This function is a cancellation pointfor the calling thread. [Note: The mainthread can not be canceled even at a cancellation point. --end note]

template<class Predicate>
    void wait(lock_type& lock,Predicate pred);

Effects: While pred() returns false callswait(lock).

Note: There is no blocking if pred() isinitially true.

booltimed_wait(lock_type& lock, const utc_time& abs_time);

Precondition: The lock is locked by thecurrent thread-of-execution. If lock_type supports recursive locking, the lockcount is one. No other thread-of-execution is waiting on this condition objectunless lock is, or refers to, the same underlying mutex object.

Effects: Atomically blocks and releases thelock on lock. If the thread-of-execution is canceled while blocked, lock willbe locked as the thread_canceled exception propagates out. If the absolute timespecified 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_timehas already been passed at the time of the call, then false is returned. Thisthread-of-execution shall unblock when another thread issues a notification tothis blocked thread. The current thread-of-execution may unblock and returneven in the absence of a notification.

Postcondition: lock is locked by the currentthread-of-execution.

Returns: true if the call to timed_wait isnotified prior to the indicated timeout, otherwise returns false.

Throws: thread_canceled, system_error. Iflock_type has an owns() member function and lock.owns() returns false uponentry, a lock_error is thrown.

Thread safety: Calls to the wait, timed_wait,notify_one or notify_all member functions of the same condition object fromdifferent threads-of-execution shall not result in data races or deadlocks.

Remarks: This function is a cancellation pointfor the calling thread. [Note: The mainthread can not be canceled even at a cancellation point. --end note]

template<class _Predicate>
    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() isinitially true, even if the timeout has already expired. The return valueindicates whether the predicate evaluates to true, regardless of whether thetimeout was triggered.

The specializationcondition<mutex> 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 designof this threading library is based on William Kempf's Boost.Thread Library, asrefined by literally hundreds of other Boost users and contributors. Dinkumwareand Metrowerks (now Freescale) implementations of Boost.Thread, developed respectivelyby Pete Becker and Howard Hinnant, created further existing practice. Proposalsby Pete Becker, Peter Dimov, Ion Gaztañaga, and Anthony Williams were alsoinfluential. Peter, Ion, and Anthony also contributed numerous critiques,suggestions, and comments on the current proposal, as did other members of anad hoc threads working group.

 

Pastedfrom <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2320.html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值