[cpp-threads] Asynchronous Function Proposal

Lawrence Crowl Lawrence at Crowl.org
Wed Jun 3 01:36:08 BST 2009


On 6/2/09, Herb Sutter <hsutter at microsoft.com> wrote:
> Summary of my main suggested changes to the proposal:
>
>  - The async policy should be an enum (or similar), not a set of
>    tag types.
>
>  - Remove variadic overloads, put async policy last with a
>    default arg.  (Can't do this with tag types.)
>
>  - async() should return a unique_future, not a new kind of future
>    (a new type is overkill and creates problems, e.g., that the
>    current proposal doesn't allow getting a shared_future to an
>    async() call; that problem was already solved by unique_future).
>
>  - The synchronous policy should semantically mean executing at
>    the call point, not at get().
>
> Lawrence wrote:
> > But those policies are what I proposed,
>
> Yes and no:
>
> Yes, you proposed those three and I agree those are the right
> ones. I was responding to Peter's note about more kinds of policies
> being 'core' (they aren't to me).
>
> But no, in the proposal they are not enumerators. Peter made a
> good suggestion that these be enums or otherwise easy to specify at
> run time, which also avoids proliferating overloads of tag types.

We have the same understanding.  I have no objection to making the
policy a enum.

However, I did get a suggestion from Jeffrey Yasskin to make the
policy object more visible.  Something like...

int parallel_sum(int* data, int size)
{
  int sum = 0;
  if ( size < 1000 )
    for ( int i = 0; i < size; ++i )
      sum += data[i];
  else {
    std::async_policy_whatever policy_obj;
    auto handle = std::async(policy_obj, parallel_sum,
                             data+size/2, size-size/2);
    sum += parallel_sum(data, size/2);
    sum += handle.get();
  }
  return sum;
}

Which would then permit associating the thread-local destruction
with the policy object, if necessary.  Furthermore, one could
pass the policy object in.

int parallel_sum(std::async_policy& policy_obj, int* data, int size)
{
  int sum = 0;
  if ( size < 1000 )
    for ( int i = 0; i < size; ++i )
      sum += data[i];
  else {
    auto handle = std::async(policy_obj, parallel_sum,
                             data+size/2, size-size/2);
    sum += parallel_sum(data, size/2);
    sum += handle.get();
  }
  return sum;
}

{ ...
  std::async_policy_whatever policy_obj;
  parallel_sum( policy_obj, gonzo_array, 1000000000 );
  ...
}

Comments?

> > where "this thread" is defined as the thread that calls get().
>
> I didn't notice that -- I think that's wrong.  First, the
> synchronous case should be the same as if the user hadn't written
> async() around it.

First, the thread that calls get() will nearly always be the thread
that calls async(), so I don't think it matters much in anything
other than a standards sense.

Second, we cannot make async behave the same as not writing the
async in the case where execution is not serial.  What we can do,
and what I propose, is to make the async behave the same whether the
policy is threaded, serial, or optional.  As I said in the paper,
if we do as you suggest, the programmer must add more catch clauses
to the code, and I don't think that is the right design.

> Second, there can be multiple gets due to shared_future (see
> below).
>
> Also, what do you think of the other suggested changes:
>
>  - The policy parameter should go last and have a default argument,
>    for symmetry with other stdlib practice (e.g., atomic<T>
>    memory order policies).

That approach is fine with me if we can make async() work as does
the std::thread constructor.

>  - Therefore, and for other reasons, we shouldn't provide
>    the variadic form, and instead provide only the one form
>    "future<...> async( F&&, AsyncPolicy = could_be_sync_or_async".

What will std::thread look like?  Who is writing the paper to
modify it?

>  - We shouldn't propose a third future type. This one should
>    return unique_future (and that and shared_future should be
>    fixed if deemed necessary). Note that this fixes a usability
>    issue with the current proposal, namely that the user can't
>    put the result in a shared_future if copying is required.

Can you provide a use case for why this flexibility is helpful?
After all, the original call was single-point as well.

The choice here is whether we require an async that cannot be
implemented in terms of the other existing interfaces.  I am
not necessarily opposed to doing so, but it sure feels like we
are shuffling issues under the carpet.  So, what I proposed can
be implemented in terms of existing interfaces.  If we drop that
requirement, we can avoid async_future.  But in doing so, I do not
want the other issues lost.

-- 
Lawrence Crowl



More information about the cpp-threads mailing list