Document number: N2185=07-0045
Programming Language C++, Evolution/Library
 
Peter Dimov, <pdimov@pdimov.com>
 
2007-03-06

Proposed Text for Parallel Task Execution

I. Overview

This document proposes an addition to Chapter 30 [threads] that allows easy and straightforward parallel execution of tasks. The mechanism of transporting the result to the caller is based on N2096, Transporting Values and Exceptions between Threads. The execution syntax is inspired by N1875, C++ Threads, by Lawrence Crowl. The exception propagation depends on the language support proposed in N2179, but the prototype implementation uses a library approximation. The cancelation support depends on the thread handle class proposed in N2178, but should be implementable under competing proposals as well.

II. Example Use

Given the sequential example:

double f( double a, int n )
{
    double r = 0.0;

    for( int i = 1; i <= n; ++i )
    {
        double x = 1.0 / i;
        r += std::pow( x, a );
    }

    return r;
}

int main()
{
    double m1 = f( 1.0, 1000000 );
    double m2 = f( 1.0, 5000000 );
    double m3 = f( 2.2, 1000000 );
    double m4 = f( 2.2, 5000000 );

    std::cout << m2 - m1 + m3 - m4 << std::endl;
}

this proposal allows a programmer to switch to parallel execution as follows:

int main()
{
    std::future<double> fm1 = std::fork( f, 1.0, 1000000 );
    std::future<double> fm2 = std::fork( f, 1.0, 5000000 );
    std::future<double> fm3 = std::fork( f, 2.2, 1000000 );
    std::future<double> fm4 = std::fork( f, 2.2, 5000000 );

    std::cout << fm2 - fm1 + fm3 - fm4 << std::endl;
}

The notation can be shortened by using the auto keyword:

    auto fm1 = std::fork( f, 1.0, 1000000 );

A function might return a type that is expensive to copy:

std::vector< std::string > tokenize( std::string const & input );

In this case, the programmer can use an explicit notation that expresses intent to destructively move the result value from the future:

    auto fs1 = std::fork( tokenize, std::ref( input ) );
    auto s1 = fs1.move();

    std::future< std::vector< std::string > && > fs2 = std::fork( tokenize, std::ref( input ) );
    std::string s2 = fs2;

Of course, the result can only be moved once.

If the function that is passed to fork throws an exception, it is stored into the future and thrown when its result is obtained:

int f()
{
    throw std::runtime_error( "Hello future world!" );
}

int main()
{
    std::future< int > fi = std::fork( f );

    try
    {
        std::cout << fi << std::endl;
    }
    catch( std::exception const & x )
    {
        std::cout << x.what() << std::endl;
    }
}

The above program prints "Hello future world!".

III. Proposed Text

Add to the synopsis of <thread> the following:

namespace std {

    class future_result_moved: public exception;
    class fork_canceled: public exception;

    template< class R > class future;
    template< class R > class future< R& >;
    template< class R > class future< R&& >;
    template<> class future< void >;

    template< class Fn > future< typename result_of<Fn()>::type > fork( Fn fn );
    template< class Fn, class A1, ..., class An > future< typename result_of<Fn(A1, ..., An)>::type > fork( Fn fn, A1 a1, ..., An an );

}

Add the following at the appropriate place of chapter 30 after the synopsis:

class future_result_moved: public exception
{
public:

    char const * what() const throw();
};

The exception future_result_moved is thrown by future<R&&> if an attempt is made to obtain its result more than once.

char const * what() const throw();

Returns: "std::future_result_moved".

class fork_canceled: public exception
{
public:

    char const * what() const throw();
};

The exception fork_canceled is placed by fork( f ) or fork( f, a1, ..., aN ) into the future result if the thread executing f() or f( a1, ..., aN ) is canceled.

char const * what() const throw();

Returns: "std::fork_canceled".

template< class R > class future
{
public:

    future();
    
    future( future const & rhs );
    future & operator=( future const & rhs );
    
    ~future();

    bool has_value() const;
    bool has_exception() const;

    void join();
    bool try_join();
    bool timed_join( timespec const & abstime );
    
    void cancel();

    operator R() const;
    R get() const;
    R move();

    void set_value( R const & r );
    void set_value( R && r );
    void set_exception( exception_ptr p );

    void set_cancel_handle( thread::handle const & th );
};

The class template future<R> represents a future result value of type R. A producer thread can put a value or an exception into a future<R> via its member functions set_value and set_exception. A consumer thread can wait for the result to become available via the join member functions, or obtain the result via the member function get or the conversion operator.

future<R> has reference semantics and all of its copies refer to the same underlying shared state. Its operations except the destructor and the assignment operator can be invoked concurrently from multiple threads. It can be likened to a shared_ptr to a thread-safe container of a single element of type R.

future();

Effects: constructs a future whose associated state holds no value, no exception and a null thread handle.

Postconditions: !has_value() && !has_exception().

future( future const & rhs );
future & operator=( future const & rhs );

Postconditions: *this refers to the same state as rhs.

Throws: nothing.

~future();

Effects: destroys *this and its associated state if no other instance refers to it.

Throws: nothing.

bool has_value() const;

Returns: true if the state associated with *this contains a value, otherwise false.

bool has_exception() const;

Returns: true if the state associated with *this contains an exception, otherwise false.

void join();

Effects: blocks until the state associated with *this contains a value or an exception. Cancelation point.

Throws: thread_cancel.

bool try_join();

Returns: true if the state associated with *this contains a value or an exception, false otherwise.

Throws: nothing.

void timed_join( timespec const & abstime );

Effects: blocks until the state associated with *this contains a value or an exception or until abstime is reached. Cancelation point.

Returns: true if the state associated with *this contains a value or an exception, false otherwise.

Throws: thread_error, thread_cancel.

void cancel();

Effects: calls thread::cancel on the thread handle of *this's associated state.

[Note: if the thread handle is null, thread::cancel is a no-op.]

operator R() const;
R get() const;

Effects: blocks until the state associated with *this contains a value or an exception. Cancelation point unless the state already contains a value or an exception.

Returns: the stored value.

Throws: the stored exception, thread_cancel, or any exception thrown by the copy constructor of R.

R move();

Effects: blocks until the state associated with *this contains a value or an exception. Cancelation point unless the state already contains a value or an exception.

Returns: the stored value r_ by moving from it as if via return std::move( r_ ). Stores an exception of type future_result_moved into *this's associated state.

Throws: the stored exception, thread_cancel, or any exception thrown by the move constructor of R.

void set_value( R const & r );
void set_value( R && r );

Effects: if *this's associated state contains no value and no exception, stores the value r into the state. If the attempt to copy r into the state throws, stores the thrown exception instead as if by using set_exception( current_exception() ). Sets the thread handle of the associated state to null.

Throws: nothing.

void set_exception( exception_ptr p );

Effects: if *this's associated state contains no value and no exception, stores the exception p refers to into the state. Sets the thread handle of the associated state to null.

void set_cancel_handle( thread::handle const & th );

Effects: sets the thread handle of *this's associated state to th.

template< class R > class future< R & >
{
public:

    future();
    
    future( future const & rhs );
    future & operator=( future const & rhs );
    
    ~future();

    bool has_value() const;
    bool has_exception() const;

    void join();
    bool try_join();
    bool timed_join( timespec const & abstime );
    
    void cancel();

    operator R &() const;
    R & get() const;

    void set_value( R & r );
    void set_exception( exception_ptr p );

    void set_cancel_handle( thread::handle const & th );
};

The specialization future<R&> stores a reference to R instead of a value of type R and has a different signature of set_value. It is otherwise identical in behavior to the primary template.

template< class R > class future< R && >
{
public:

    future();
    
    future( future const & rhs );
    future( future< R > const & rhs );
    future & operator=( future const & rhs );

    ~future();

    bool has_value() const;
    bool has_exception() const;

    void join();
    bool try_join();
    bool timed_join( timespec const & abstime );

    void cancel();

    operator R ();
    R get();
    R move();

    void set_value( R const & r );
    void set_value( R && r );
    void set_exception( exception_ptr p );

    void set_cancel_handle( thread::handle const & th );
};

The specialization future<R&&> is identical in behavior to future<R>, except as stated below:

future( future< R > const & rhs );

Postconditions: *this refers to the same state as rhs.

operator R();
R get();

Returns: move().

template<> class future< void >
{
public:

    future();
    
    future( future const & rhs );
    future & operator=( future const & rhs );
    
    ~future();

    bool has_value() const;
    bool has_exception() const;

    void join();
    bool try_join();
    bool timed_join( timespec const & abstime );

    void cancel();

    void get() const;

    void set_value();
    void set_exception( exception_ptr p );

    void set_cancel_handle( thread::handle const & th );
};

The specialization future<void> is identical in behavior to the primary template, except as stated below:

void get() const;

Effects: blocks until the state associated with *this contains a value or an exception. Cancelation point unless the state already contains a value or an exception.

Throws: the stored exception, or thread_cancel.

[Note: this allows generic code to use f.get() in a return statement, even when f is future<void>.]

[Example:

template<class R> R generic_function( future<R> ft )
{
    return ft.get();
}

--end example. ]

void set_value();

Effects: if *this's associated state contains no value and no exception, stores the value 0 into the state. Sets the thread handle of the associated state to null.

Throws: nothing.

template< class Fn > future< typename result_of<Fn()>::type > fork( Fn fn );

Requires: The expression fn() must be valid and have a type convertible to R, where R is typename result_of<Fn()>::type.

Effects: Evaluates the expression fn() asynchronously with respect to the calling thread and places its result in an object ft of type future<R> as if by using ft.set_value( fn() ). If the expression fn() throws an exception e, places e into ft as if by using ft.set_exception( current_exception() ). If e is of type thread_cancel, translates it to an exception of type fork_canceled before placing it into ft.

Returns: ft.

template< class Fn, class A1, ..., class An > future< typename result_of<Fn(A1, ..., An)>::type > fork( Fn fn, A1 a1, ..., An an );

Returns: fork( bind( fn, a1, ..., an ) ).

IV. Implementability

A prototype implementation is available at:


--end