[cpp-threads] Asynchronous Function Proposal

Lawrence Crowl Lawrence at Crowl.org
Thu Jun 18 00:22:22 BST 2009


On 6/16/09, Herb Sutter <hsutter at microsoft.com> wrote:
> Attached is a first draft of my paper that's a
> complement/competitor to N2889.
>
> Comments and criticisms are welcome!

Thanks for writing this up.  I think I better understand the
disagreement now.  I'm commenting from the update.

> Brief responses to Lawrence (most other responses are in the
> paper, and may or may not be correct so please let me know if I
> got anything egregiously or tinily wrong):

More later.

> > > - The async policy parameter is still first, and we don't
> > > need a variadic overload. There should be just one async()
> > > with a defaulted policy parameter.
> >
> > I have no objection to this approach a-priori, but I think the
> > async parameters should be consistent with the thread constructor
> > parameters.  If thread changes, the async should change as well.
>
> I don't see what you're referring to in 30.3 (looking at N2857). Is
> there a separate paper adding constructor parameters? I don't see
> "fully_threaded" etc. anywhere.

The current working draft has in 30.3, three overloads for the
constructor.  The first is not relevant.

thread();
template <class F> explicit thread(F f);
template <class F, class ...Args> thread(F&& f, Args&&... args);

Consistency is important to a language, and being able to specify
arguments in one way in one place, and not in a seemingly similar
place causes confusion.  To have these to parameters for async, the
policy parameter cannot be at the end, which leaves the beginning.

> > > - The wording for the first policy still says "in a new
> > > thread," which prevents the implementation from caching threads
> > > in any way (including the highly desirable option of running
> > > on a thread pool). It should say "in another thread."
> >
> > N2880 says why allowing thread caching is not feasible.  We can
> > do caching when we have thread pools, but I don't believe that
> > we can do so until then.
>
> Maybe I'm missing something: I don't see how this is specific to
> async so as to influence the async design. As Peter and others
> have pointed out, this use-after-destruction issue has nothing
> to specifically with async, nor even specifically with threads
> at all, but is a general and preexisting issue with globals and
> statics. Am I justified in taking a "nothing to see here" view? I
> don't want to be wrongly dismissive, but I don't see what those
> (valid) issues have to do with async.
>
> See further notes in the attached.

Here is the crux of the problem.  We view use-after-destruction
as a programmer problem.  However, if the language provides no
mechanism for the programmer to prevent use-after-destruction, then
it is a language problem.  My whole async design is a consequence
of providing that mechanism.  Remember, I _started_ with a proposal
similar to yours, N2880 is the explaination for why it didn't work,
and N2889 is the workaround given that we cannot have thread pools.
(I'm beginning to really regret the Kona compromise.)

Being specific, there must be a way to join with any thread launched.
Otherwise, you have no mechanism to prevent use-after-destruction.
There are two approaches.

First, you can use a parameter to async to specify an object that
will join the threads.  This approach is the thread-pool approach.
You associate the thread lifetime with the pool.  We agreed not to
take this approach in the Kona compromise.

Second, you can associate the thread lifetime with the returned
value.  I have done so with the proposed new future.  You have not
done so.  We could associate the thread lifetime with an existing
future.  However, this association would constitute a radical change
to the current future types.  I see no such change in your paper.
In particular, it is incompatible with your desire for a thread-pool
implementation.

That is, I think your proposal is technically incomplete.

On the other hand, while my proposal is technically complete
(I think), it needs more clarification to address that point.
In particular, the name "async_future" derives from its motivation,
not its purpose.  I will be renaming the future to joining_future.

> > BTW, the OS can still cache threads underneath, but it still
> > has to destroy and reconstruct the thread-local variables.
> >
> > > - There is still a new future type, async_future. It should be
> > > unique_future.  (In related news, the return type of async()
> > > should be convertible to shared_future. Adding a conversion
> > > from async_future to shared_future would satisfy that part,
> > > but not satisfy the main point in this bullet.)
> >
> > Can you provide a rationale based on use cases?
>
> Anything that uses a shared_future is a use case. As I mentioned
> recently, the key is that we want to pass a value somewhere, and
> sometimes the target needs the value immediately and sometimes
> it needs it eventually. From my note on 6/3:

No, I don't believe "anything that can use shared_future" is a use
case, in part because of the issues above.  Show me code that solves
a problem.  I agree that the mechanisms can be used in arbitrary
ways, but not all of them make sense.

> - Anytime the result is of interest to more than one thread. You
> - don't want to block-then-share, you want to pass everyone a
> - future so everyone can keep running concurrently until they
> - need the result. In dataflow terms, think of any example where
> - we split the pipe two or more ways.

I can image all sorts of code like that, but I believe such code
will require so much more scaffolding that async itself would
provide no benefit to the programmer.

> > > > The proposed solution consists of a set of async functions
> > > > to launch asychronous work and a new kind of future to
> > > > manage the function result.  This solution derives from
> > > > an extensive discussion on the C++ threads standardisation
> > > > <cpp-threads at decadentplace.org.uk> mailing list.
> > >
> > > That wrongly implies that the discussion blessed/suggested
> > > both the "set of async functions" and "a new kind of future"
> > > design points -- both of which I and others have objected
> > > to. At minimum this needs to acknowledge that there were
> > > strong objections against both of these features.
> >
> > Note that I said discussion, not consensus.  It was my intent
> > to capture the discussion and choices in the document.  If you
> > feel that I have not done so accurately, please send me text.
>
> Add something like: "In that discussion there were strong
> objections to both the 'set of async functions' and particularly to
> 'a new kind of future' design points proposed in this paper." (For
> now I've put in a placeholder objection to this in the attached
> but desire to remove it.)

I will send an updated draft of my paper.

> > Unfortunately, packaged tasks and futures are only part of the
> > specification.  The issues presented in N2880 are handled in the
> > code around those primitives.  That is, by providing a set of very
> > primitive mechanisms, packaged task et al punt on the hard issues.
> > By introducing async, we are squeezing out that code, and hence
> > need to address those issues.
>
> If you think that my comments in the attached on N2880 are missing
> something, I'd really like to understand it and could you explain
> what it is?

I have tried above.  A chat might be helpful.

> Notes on your proposal's proposed wording:
>
> - several places still talk about a policy type, that should be
> 'enumerator' now

Thanks.  Fixed.

> - is the final note really true?

If you mean "Note: as described above, the template and its two
required specializations differ only in the return type and return
value of the member function get.", then yes.  It is in the first
paragraph immediately after the synopsis.

I have more comments on the paper, particularly with erroneous
conclusions from my draft, but I'll leave them for another message.

-- 
Lawrence Crowl



More information about the cpp-threads mailing list