[cpp-threads] RE: "Agenda" for august 23-25 concurrency meeting
Howard Hinnant
hinnant at twcny.rr.com
Thu Aug 31 21:48:32 BST 2006
On Aug 31, 2006, at 3:36 PM, Peter Dimov wrote:
>>> Is
>>>
>>> future< vector<int> > fv = ex.execute( bind( compute, input
>>> data ) );
>>> vector<int> v = fv.move(); // second move() or subsequent op()
>>> throws value_already_moved
>>>
>>> good enough? It'd work for any asynchronous function call, not just
>>> a dedicated thread, but the syntax is a bit clumsier (and the
>>> implementation somewhat fragile but doable.)
>>
>> This design turns what I proposed to make a compile time error into a
>> run time error.
>
> I must be missing something.
>
> thread<vector<int> > t = launch_thread(bind(compute, input_data));
> vector<int> v = t();
> vector<int> v2 = t(); // ?
>
> How can you make the third line a compile-time error, but not the
> second?
Sorry, my bad. I rewrote my reply several times and my final version
was both unclear and incorrect.
Yes, what you show is a run time error too. My train of thought went
something like:
If I'm using future<T> I may or may not be using a design philosophy
of single ownership of the return value (so that I can move out of
it). Upon inspecting code (that perhaps I'm unfamiliar with) I need
to search for use of future<T>::move, and also search for multiple op
() calls to a future, *and* any of that future's copies, to ensure
that the code is well behaved.
If I'm using thread<T>, I know I've got unique ownership of the
return value. I should only see use of op() in one place. If I see
op() on a thread more than once, or from a moved-from thread, I know
immediately I've got a mistake and don't have to further revisit the
design of this part of the code. I.e. sometimes a shared_ptr is an
appropriate design, and sometimes a unique_ptr (auto_ptr) is
appropriate. And having two types to serve the two designs is more
likely to result in easily understandable code. Additionally, the
fact that a thread isn't copyable means accidental copies will be
caught at compile time (that's why unique_ptr is better than auto_ptr):
thread<R> t = ...
generic_func(t); // oops, meant move(t), error flagged by compiler
---
fixed:
thread<R> t = ...
generic_func(std::move(t));
// it is now more obvious that t() here would be a mistake
Contrast with:
future<R> t = ...
generic_func(t);
// Did generic_func steal my return value?
That's not to say you shouldn't send a future to a generic function.
Indeed I'm sure that such use will be very useful. It's just to say
that sometimes you need a shared_ptr and sometimes you need a
unique_ptr. :-) And I think it would be error prone to combine those
two into one type that sometimes displayed shared ownership and
sometimes unique ownership.
>
>> It also destroys efficiency for generic code getting
>> an asynchronous value.
>>
>> template <class T, class Pred>
>> void foo(Pred p)
>> {
>> ...
>> T temp = p(); // if asynchronous, inefficiency mandated?
>> // or:
>> T temp = p().move(); // No longer generic, Pred must be a future
>> ...
>> }
>
> Agreed... but...
>
> template <class T, class Pred>
> void foo(Pred p)
> {
> ...
> T temp = p();
> T temp2 = p(); // ?
> }
>
> This code is fine when p is a future.
Yes. If generic code is known to evaluate the functor more than once
(like std::sort does with the Compare), it would be inappropriate to
send it a thread (and concepts might even catch such an error, not
sure).
>> One can never build efficient code on top of an inefficient base. If
>> there is no efficient layer in the threading API, then we have made
>> the performance/feature tradeoff decision for all C++ clients in this
>> area. I'd far rather provide only an efficient base layer upon which
>> clients can build their own feature-rich code, than provide only a
>> feature-rich (but expensive) API. If we can provide both layers,
>> great (and I think we can). But there is no question which layer is
>> more important for us to provide (imho).
>
> template<class R> class thread
> {
> private:
>
> future<R> ft_;
>
> public:
>
> template<class F> explicit thread( F f )
> {
> ft_ = threaded_executor().execute( f );
> }
>
> R operator()()
> {
> return ft_.move();
> }
> };
>
> ?
Right, now we have an efficient *interface*. The fact that it was
implemented inefficiently is not a standards issue. ;-)
> Also, the inefficiency does not manifest itself that often. If you
> have a
>
> vector< thread< vector<int> > > v;
>
> , fill it with async calls and then retrieve the return values in
>
> vector< vector<int> > v2;
>
> you only gain when you later need to operate on v2 in place.
v2 as a whole is movable, as is each individual element. I.e.
vector< vector<int> > is as efficient, or as expensive as the client
needs it to be.
> In the immutable case,
>
> vector< future< vector<int> > > v;
>
> (with the R const& return modification) is as efficient as the
> above, since the results are contained within the futures and there
> is no need to copy them out.
I'm all for the R const& return from future as it provides exactly
this option (of zero-expense const viewing). Note here though that
you are either limited to that const view, or you need to pay for a
copy (which is fine if that is by your application's design).
> There's also the thread creation overhead that can easily dominate
> the vector<int> copy, but I'll be willing to assume for the sake of
> the argument that the underlying library pools the threads and the
> vectors are sufficienty large. :-)
Good. The copy of a return can be arbitrarily expensive.
>
> I never explored the possibility of a noncopyable but movable
> future, by the way.
In a nutshell, this is what I'm after, but under a different name.
I want to make:
T foo();
callable either synchronously or asynchronously with the same
complexity cost, even if T is a heavy container. It would be a shame
if we brought threads to C++ (in the name of efficiency to make use
of all those processors) and then made them more difficult than need
be to use efficiently. futures are good too. But they are a layer/
level up from where I'm concerned.
-Howard
More information about the cpp-threads
mailing list