Document number: N2184=07-0044

Howard E. Hinnant
2007-03-09

Thread Launching for C++0X

Contents

Introduction

This paper concentrates on the basic thread launching layer for C++0X. This paper does not include mutex, exclusive_lock or condition. These are covered in N2094. Nor does this paper cover future or thread_pool. That is not because I do not support these features. This paper does not cover One-time object initialization, but I believe we want it.

Covered herein is just std::thread.

The paper assumes a fair amount, such as motivation for multithreading in C++. In an effort to be brief, this paper concentrates on why this std::thread is the foundation suitable for C++0X. This paper neither proposes nor opposes a C-level threading API.

Goals

The std::thread proposed herein is not designed to be the ultimate thread launching facility. Rather it is designed to be a solid foundation upon which higher level functionality can be non-intrusively layered. There will probably be things you wished this std::thread directly supported. Be assured that impressive higher level functionality has been shown to be portably implementable on top of this layer. The purpose of this proposed std::thread is to provide a thin encapsulation over the OS thread.

It provides:

It does not provide the following; these features are expected in a higher level library:

The design of this thread is an evolution from boost::thread. The intent is to reuse as much of the boost::thread experience as possible, keeping the good, and changing the very minimum necessary to respond to the known limitations of this field experience.

Examples

This section introduces common use cases of thread with simple examples. See the synopsis for a brief overview of the entire proposal. And see the specification for a detailed description of each item in the synopsis.

Creating a thread

A thread is launched by constructing a std::thread with a functor:

#include <thread>

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(). Any return type is ignored.

#include <thread>

struct f
{
    double operator()() const;
};

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

Joining with a thread

A std::thread can be joined with with one of two syntaxes:

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

or

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

One can test if a thread is joinable. If the thread has 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 a std::thread object to join with. And it is not possible to create a std::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 wrap f 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 <fstream>
#include <thread>
#include <functional>

void f()
{
    throw 1;
}

struct log_uncaught_exceptions
{
    template <class F>
    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:

thread is move-only

A std::thread is not copyable, but is moveable. This ensures that a thread can 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::vector<std::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<thread> 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 an id 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_id name 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 a thread_canceled exception with this syntax:

std::thread t(f);
t.cancel();  // 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.cancel() 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:

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 cancel()

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.

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. For example:

SomeClass::~SomeClass()
{
    std::this_thread::disable_cancellation _;
    t_.join();
}

or

SomeClass::~SomeClass()
{
    try
    {
        t_.join();
    }
    catch (...)
    {
    }
}

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 and object of type this_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_cancellation object:

void foo()
{
    std::this_thread::disable_cancellation no_cancel;
    A a;
    std::this_thread::enable_cancellation cancel_ok(no_cancel);
    a.foo();  // this may be cancelled
}  // disable cancellation, a.~A(), enable cancellation

The enable_cancellation constructor simply reverts to the cancellation state that was in effect with the referenced disable_cancellation object was constructed. Thus enable_cancellation doesn't actually enable cancellations unless the referenced disable_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), a thread_canceled exception propagates through the stack frame, destructing t2 before it has had a chance to join. If t2 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 thread effectively not being honored. Thus when a thread is destructed, if it is joinable then it is first canceled, 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 cancel(); detach(); are desired for a thread destructor, 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();
void sleep(std::nanoseconds rel_time);

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: std::nanoseconds is taken from N2058 which is currently targeted to TR2. It is recommended that a minimal subset of N2058 be standardized to support the needs of the threading library.

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.

Synopsis

// <thread>

namespace std
{

// Thread cancellation notification

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

// Unable to allocate thread-related resources

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

// A move-only thread handle

class thread
{
private:
    // not copyable
    thread(const thread&);
    thread& operator=(const thread&);

public:

    // construction, destruction
    thread();
    template <class F> explicit thread(F f);
    ~thread();

    // move semantics
    thread(thread&& x);
    thread& operator=(thread&& x);

    void swap(thread& x);

    // thread identity
    class id
    {
    public:
        id();
        friend bool operator==(const id& x, const id& y);
        friend bool operator!=(const id& x, const id& y);
    };

    id get_id() const;

    // join-related members
    bool joinable() const;
    void join();
    void operator()();
    void detach();

    // cancel-related members
    void cancel();
    bool cancellation_requested() const;

    // a measure of the maximum number of threads which can run concurrently
    static unsigned hardware_concurrency();

    typedef implementation native_handle_type;
    native_handle_type native_handle();
};

namespace this_thread  // functions for affecting and inspecting the current thread
{

// disable cancellation for this thread
class disable_cancellation
{
    disable_cancellation(const disable_cancellation&);
    disable_cancellation& operator=(const disable_cancellation&);
public:
    disable_cancellation();
    ~disable_cancellation();
};

// enable cancellation for this thread
class enable_cancellation
{
    enable_cancellation(const enable_cancellation&);
    enable_cancellation& operator=(const enable_cancellation&);
public:
    explicit enable_cancellation(disable_cancellation& d);
    ~enable_cancellation();
};

// identity for this thread
thread::id get_id();

// cancel-related functions for this thread
void cancellation_point();
bool cancellation_enabled();
bool cancellation_requested();

// cooperative threading functionality
void yield();
void sleep(std::nanoseconds rel_time);

}  // this_thread

}  // std

Specification

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-deļ¬ned NTBS.

Throws: Nothing.

Remarks: The message may be a null-terminated multibyte string (17.3.2.1.3.2), suitable for conversion and display as a wstring (21.2, 22.2.1.4). 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
{
private:
    // not copyable
    thread(const thread&);
    thread& operator=(const thread&);

public:

    // construction, destruction
    thread();
    template <class F> explicit thread(F f);
    ~thread();

    // move semantics
    thread(thread&& x);
    thread& operator=(thread&& x);

    void swap(thread& x);

    // thread identity
    class id
    {
    public:
        id();
        friend bool operator==(const id& x, const id& y);
        friend bool operator!=(const id& x, const id& y);
    };

    id get_id() const;

    // join-related members
    bool joinable() const;
    void join();
    void operator()();
    void detach();

    // cancel-related members
    void cancel();
    bool cancellation_requested() const;

    // a measure of the maximum number of threads which can run concurrently
    static unsigned hardware_concurrency();

    typedef implementation native_handle_type;
    native_handle_type native_handle();
};
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 <class F> explicit thread(F f)

Effects: Constructs an object of type thread and executes the functor f asynchronously. F is a functor which takes no argument. Any return value is ignored.

Postconditions:

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

If the newly referenced thread immediately executes this_thread::cancellation_enabled() the return is true.

Throws: thread_resource_error if unable to acquire the resources to start this thread.

~thread()

Effects: If joinable() then cancel() followed by detach(), else there are 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 that x.joinable() did prior to the construction. get_id() returns the value that x.get_id() did prior to the construction.

Throws: Nothing.

thread& operator=(thread&& x)

Effects: Assigns the state 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 that x.joinable() did prior to the assignment. get_id() returns the value that x.get_id() did prior to the assignment.

Throws: Nothing.

void swap(thread& x)

Effects: Swaps the state of *this and x.

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 refer to not any thread, then returns true. Else if x and y refer to the same thread, then returns true. Else returns false.

Throws: Nothing.

bool operator!=(const id& x, const id& y)

Returns: !(x == y)

Throws: Nothing.

id get_id() const

Returns: A thread::id which refers to this thread. If this thread is not joinable() returns a default constructed id.

Throws: Nothing.

bool joinable() const

Returns: get_id() != id().

Throws: Nothing.

void join()

Preconditions: joinable() is true.

Effects: This thread blocks until the thread referenced by thread completes.

Postconditions: After a normal return of join(), joinable() is false. An exceptional return will indicate that the calling thread has received a request to cancel. In such an event, the thread referenced by *this remains unaffected.

Throws: If this_thread::cancellation_requested() returns true during the call to join(), throws thread_canceled.

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]

void operator()()

This is an alias for join() with the same Preconditions, Effects, Postconditions, and Throws clauses.

void detach()

Preconditions: joinable() is true.

Effects: The referenced thread is allowed to continue execution with no referencing thread. When the referenced thread ends execution it will clean up any resources owned by the thread. *this no longer references this thread of execution.

Postconditions: joinable() is false.

Throws: Nothing.

void cancel()

Preconditions: joinable() is true.

Effects: The referenced thread receives a request to throw an exception of type thread_canceled at the next cancellation point it executes where thread_self::cancellation_enabled() returns true as executed by the referenced thread.

Throws: Nothing.

bool cancellation_requested() const

Preconditions: joinable() is true.

Returns: If the referenced thread has received a request to throw an exception of type thread_canceled at the next cancellation point and has not yet done so, returns true else returns false.

Throws: Nothing.

unsigned hardware_concurrency()

Returns: A measure of the number of threads which could possibly execute concurrently. This number should just be considered a hint. On platforms where this information is not computable or well defined a return value of 1 is recommended, but not required.

Throws: Nothing.

native_handle_type native_handle()

Returns: An implementation defined type representing the underlying OS thread handle.

Throws: Nothing.

namespace this_thread
{
class disable_cancellation
{
    disable_cancellation(const disable_cancellation&);
    disable_cancellation& operator=(const disable_cancellation&);
public:
    disable_cancellation();
    ~disable_cancellation();
};
}
disable_cancellation()

Effects: Constructs an object of type disable_cancellation. The construction has the effect of disabling requests to cancel this thread from other threads during the lifetime of this object (except as modified by enable_cancellation). When a cancellation point is executed within the lifetime of this object, a request to cancel has no affect (except as modified by enable_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 this disable_cancellation was constructed.

Throws: Nothing.

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

namespace this_thread
{

class enable_cancellation
{
    enable_cancellation(const enable_cancellation&);
    enable_cancellation& operator=(const enable_cancellation&);
public:
    explicit enable_cancellation(disable_cancellation& d);
    ~enable_cancellation();
};

}
enable_cancellation(disable_cancellation& d)

Effects: Constructs an object of type enable_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.

~enable_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.

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 this_thread::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. Else returns true unless a disable_cancellation object has been constructed (and not destructed) and which has not been reverted with a enable_cancellation object.

Throws: Nothing.

bool cancellation_requested()

Returns: true if a cancel() has been called on this thread and the thread has not yet executed a cancellation point with cancellation enabled.

Throws: Nothing.

void yield()

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

Throws: Nothing.

void sleep(std::nanoseconds rel_time)

Effects: This thread blocks for at least the amount of time specified, unless this thread receives a request to cancel.

Throws: Nothing.

Remarks: This function is a cancellation point.

Acknowledgments

This subject of this paper is controversial enough that there are probably as many different positions as there are people. I would like to gratefully acknowledge the help and suggestions from the following people without implying support from anyone for this proposal.

Thanks to Matt Austern, Hans Boehm, Eric Christopher, Greg Colvin, Lawrence Crowl, Beman Dawes, Peter Dimov, Kevlin Henney, James Kanze, Herb Sutter, and Anthony Williams.