Topic: thread constructor and std::async, What do they do?
Author: SG <s.gesemann@gmail.com>
Date: Thu, 18 Feb 2010 11:19:52 CST Raw View
Can somebody explain to me what the behaviour of std::thread's
constructor template and std::async is exactly? I have trouble
interpreting the documents N3035 and N3041 in this regard.
One of thread's constructors, from N3035 (30.3.1.2):
> template<class F, class... Args>
> thread(F&& f, Args&&... args)
>
> Requires: F and Ti in Args shall be CopyConstructible if an
> lvalue and otherwise MoveConstructible. INVOKE (f, w1, w2, ...,
> wN) (20.8.2) shall be a valid expression for some values w1,
> w2, ..., wN, where N == sizeof...(Args).
>
> Effects: Constructs an object of type thread and executes
> INVOKE (f, t1, t2, ..., tN) in a new thread of execution, where
> t1, t2, ..., tN are the values in args.... [...]
>
> Synchronization: The invocation of the constructor happens
> before the invocation of f.
What are f, t1, t2, ..., tN in the effects paragraph exactly? What
objects/values do they refer to? Is there some copying/moving of the
objects/values involved or does the constructor "store the
references"? I can imagine an implementation using tuples where the
invocation will look like this:
forward<F>(f)(forward<Args>(get<Indices>(param_tuple))...);
But the question remains: What does f refer to and what does the
parameter tuple store? A reference to the original function/argument
object(s) or a copy of them?
In other words, how do I have to create the tuple and the thing that f
refers to in the other thread of execution in order to be standards-
compliant?
decltype(f) == F& (reference)
make_tuple(ref(args)...)
or
decltype(f) == decay<F>::type (copy of functor)
make_tuple(args...)
(This STILL ALLOWS storing references via explicit
use of reference wrappers at the call site.)
Or do I even need compiler magic to seperate the functor's parameter
initialization from the actual execution and guarantee that the
constructor returns AFTER the stack parameters have been initialized
and possibly BEFORE execution of the functor has begun. This is what
Scott Meyers had in mind if I remember correctly. As far as I can tell
it requires compiler magic and is also IMO dangerous w.r.t.
unintential sharing / possibly dangling references (function might
accept const references as performance tweak and not expect the
objects to be mutated by another thread or cease to exist).
This should be clarified. And if the answer is "there's no copying/
moving involved" then I have to seriously question the purpose of this
constructor. It would be seemingly useless and dangerous to use
(unintential sharing, possibly dangling references, something that is
NOT a problem in a single-threaded case).
You would expected that std::async behaves similarly with the
exception that it doesn't initialize a thread object (visibly) but
returns a future. What follows is a combination of N3000 (30.6.9) with
fixes from N3041:
> template<class F, class... Args>
> future<typename result_of<F(Args...)>::type>
> async(F&& f, Args&&... args);
>
> template<class F, class... Args>
> future<typename result_of<F(Args...)>::type>
> async(launch policy, F&& f, Args&&... args);
>
> Requires: F and each Ti in Args shall be CopyConstructible if an
> lvalue and otherwise MoveConstructible. [Editor s note: Is this
> correct? F and Ti are types; can a type be an lvalue?]
> INVOKE (f, w1, w2, ..., wN) (20.8.2) shall be a valid expression
> for some values w1, w2, ..., wN, where N == sizeof...(Args).
>
> Returns: an object of type future<typename F::result_type>. The
N3041 obviously missed to change this return type here as well.
> first function behaves the same as a call to the second function
> with a policy argument of launch::any and the same arguments for
> F and Args. The behavior of the second function depends on the
> policy argument as follows:
> - launch::async - executes INVOKE (f, w1, w2, ..., wN) as if
> in a new thread of execution represented by a thread object.
> Any return value is captured by the future object. Any
> exception propagated from f is captured by the future
> object. The thread object is associated with the future
> object and affects the behavior of the future object.
> - launch::sync INVOKE (f, w1, w2, ..., wN) is associated
> with the future object. [Editor s note: What does it mean
> for INVOKE to be associated with a future object?] The
> invocation is said to be deferred.
> - launch::any - the implementation may choose either either
> policy above at any call to async. [ Note: implementations
> should defer invocations when no more concurrency can be
> effectively exploited. end note ]
>
> Effects: If the invocation is deferred, the associated
> asynchronous state is not ready until the function has completed.
> Calls to waiting functions (wait() or other functions defined in
> terms of wait()) on asynchronous return objects that share the
> associated asynchronous state created by this async() call
> synchronize. The first of such calls shall invoke the deferred
> function in the thread that called the waiting function; all
> other calls waiting for the same result shall block until the
> deferred function has completed. The completion of the
> invocation of the deferred function happens-before the calls to
> the waiting functions return.
>
> Synchronization: the invocation of the async [Editor's note: what
> is "the async"?] happens before (1.10) the invocation of f.
> [ Note: this statement applies even when the corresponding future
> object is moved to another thread. end note ]
A lot of explaining but I still don't know what's going on. I'm
especially concerned about whether the functor and the arguments are
copied/moved "for later invocation" or just "references are stored".
Maybe I'm missing something else, but I don't see ANY use in something
that stores references to the original arguments by default for "later
invocation". I'd much rather have functors copied/moved in a tuple-
like fashion which allows us to use reference_wrappers explicitly in
case we really need a reference to the original. Some of you might
comment with a "Then just use std::bind already!" but std::bind
doesn't allow move-only types whereas std::thread and std::async COULD
EASILY support move-only functors and parameters.
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Anthony Williams <anthony.ajw@gmail.com>
Date: Fri, 19 Feb 2010 14:46:57 CST Raw View
SG <s.gesemann@gmail.com> writes:
> Can somebody explain to me what the behaviour of std::thread's
> constructor template and std::async is exactly? I have trouble
> interpreting the documents N3035 and N3041 in this regard.
I'll do my best.
> One of thread's constructors, from N3035 (30.3.1.2):
>
>> template<class F, class... Args>
>> thread(F&& f, Args&&... args)
>>
>> Requires: F and Ti in Args shall be CopyConstructible if an
>> lvalue and otherwise MoveConstructible. INVOKE (f, w1, w2, ...,
>> wN) (20.8.2) shall be a valid expression for some values w1,
>> w2, ..., wN, where N == sizeof...(Args).
>>
>> Effects: Constructs an object of type thread and executes
>> INVOKE (f, t1, t2, ..., tN) in a new thread of execution, where
>> t1, t2, ..., tN are the values in args.... [...]
>>
>> Synchronization: The invocation of the constructor happens
>> before the invocation of f.
This paragraph will be replaced by the wording from issue 929:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3018.html#929
Basically, the std::thread constructor must copy f and args... into
internal storage which is accessible to the newly created thread. This
copy of f is then invoked, passing the copy of args... as the arguments.
> In other words, how do I have to create the tuple and the thing that f
> refers to in the other thread of execution in order to be standards-
> compliant?
>
> decltype(f) == F& (reference)
> make_tuple(ref(args)...)
> or
> decltype(f) == decay<F>::type (copy of functor)
> make_tuple(args...)
> (This STILL ALLOWS storing references via explicit
> use of reference wrappers at the call site.)
The latter.
> You would expected that std::async behaves similarly with the
> exception that it doesn't initialize a thread object (visibly) but
> returns a future.
> Maybe I'm missing something else, but I don't see ANY use in something
> that stores references to the original arguments by default for "later
> invocation". I'd much rather have functors copied/moved in a tuple-
> like fashion which allows us to use reference_wrappers explicitly in
> case we really need a reference to the original. Some of you might
> comment with a "Then just use std::bind already!" but std::bind
> doesn't allow move-only types whereas std::thread and std::async COULD
> EASILY support move-only functors and parameters.
I agree that this section of the standard is still unclear. std::async
and std::thread are intended to support move-only types.
The intention is that std::async makes copies of the supplied arguments
(moving if they're rvalues), just like the std::thread constructor with
the resolution of issue 929 applied. These copies are either stored for
later invocation from future::wait(), or passed on to the new
thread. That's certainly what my implementation does.
I did suggest wording akin to issue 929, but there were a few minor
disagreements about precise wording and it didn't make it into the
paper. If the wording isn't sorted at Pittsburgh then I'll submit a
DR/paper/NB comment as appropriate to get it cleaned up.
Anthony
--
Author of C++ Concurrency in Action http://www.stdthread.co.uk/book/
just::thread C++0x thread library http://www.stdthread.co.uk
Just Software Solutions Ltd http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]