Doc. No.: | WG21/P0667R0 |
---|---|
Date: | 2017-6-18 |
Reply-to: | Hans-J. Boehm |
Email: | hboehm@google.com |
Audience: | SG1, LEWG |
The purpose of this brief paper is to facilitate discussion of whether, and in what form the futures extensions (Clause 2 [futures]) of the Concurency TS should be moved into C++20. Many users feel that they are sorely needed. But, unlike the other parts of the TS, there appear to be deep concerns about including them unchanged. Thus this is intended to start discussion about the right path here.
I will outline my concerns. There may be others.
The central component of this section is the .then()
member
function of std::future
. This runs the argument continuation when
the future becomes ready. An earlier design took an explicit argument to
specify where the continutation should be run. With the demise of the original
executor proposal, this became impossible.
As is pointed out in LWG2533, the current TS is unclear where continuations
are run. In the absence of executors, it seems reasonable to run
continuations on whatever thread or, more generally, execution agent
fufilled the promise. But this is not possible
if the future is ready at the point at which .then()
is called. Thus we focus on this important, though somewhat
unusual, case.
There seem to be at least two somewhat reasonable, but very different options.
If .then()
is used to string together a chain of I/O operations,
well-designed client code will look very different in the two cases.
.then()
continuation
synchronously in the .then()
caller. This seems to be fairly
common in existing implementations of similar functionality. This
composes reasonably well with non-standard executor implementations,
in that there is little inherent overhead in running the continuation and
the continuation code then has the option to explicitly run the
bulk of the computation with a suitable home-brew executor.
It's also the right approach when the continuation is known
to complete almost immediately.
.then()
-caller, we could instead run the
continuation in a separate thread, at least in this case. This was the
preference expressed by SG1 during the LWG2533 discusion.
It almost certainly produces the least surprising results in the
absence of home-brew executors.
As far as I have been able to determine, option (1) is more commonly
implemented, but a quick inspection of some client code suggests that it's
common to overlook the future-already-ready case, and erroneously assume that
the continuation will run in the task satisfying the promise.
If the user does carefully consider this case, it is often difficult
to handle well without a facility to cheaply move
the continuation to an execution agent running concurrently with the
.then()
. Executors are intended to provide that facility,
but are not yet ready.
I'm currently suspicious that option (1), when used by typical programmers, tends to introduce unexpected thread delays due to long-latency operations running on the wrong thread.
SG1 favored option (2) for similar reasons. But that is unlikely to remain optimal once executor use is widespread. For users who want to control where the continuation is run, it may require the creation of a thread-like execution agent, solely to invoke an executor, which then will typically use an executor to run the bulk of the continuation where it should really run. Current users with an existing executor implementation seem to dislike this option, for good reason.
Thus we have two possible interpretations of the .then()
semantics, neither of which is clearly the right default behavior.
And the correct default here, if there is one, seems intimately tied
to the design of executors.
For the sake of discussion, here are some possible options for the committee:
.then()
, but
with a change to the calling syntax that does not position it as
the default .then()
operation once we have executors.
We could either rename the function, or add a required parameter
that better specifies its behavior.
I very tentatively favor the last option here, which could allow us
to support both option 1 and option 2 from the previous section.
Syntax like
f.then(std::maybe_now,
continuation)
would defang option 1 from the last section.