Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding an example about resumable functions in proposal N3650 for C++1y

Tags:

c++

c++14

Consider the following example taken from N3650:

int cnt = 0;
do {
   cnt = await streamR.read(512, buf);
   if (cnt == 0)
      break;
   cnt = await streamW.write(cnt, buf);
} while (cnt > 0);

I am probably missing something, but if I understood async and await well, what is the point in showing the usefulness of the two constructs with the above example when the effects are equivalent to writing:

int cnt = 0;
do {
   cnt = streamR.read(512, buf).get();
   if (cnt == 0)
      break;
   cnt = streamW.write(cnt, buf).get();
} while (cnt > 0);

where both the read().get() and write().get() calls are synchronous?

like image 823
Martin Avatar asked Aug 20 '13 07:08

Martin


3 Answers

The await keyword is not equal to calling get on a future. You might look at it more like this, suppose you start from this:

future<T> complex_function()
{
     do_some_stuff();
     future<Result> x = await some_async_operation();
     return do_some_other_stuff(x);
}

This is functionally more or less the same as

future<T> complex_function()
{
     do_some_stuff();
     return some_async_operation().then([=](future<Result> x) {
         return do_some_other_stuff(x);
     });
}

Note the more or less, because there are some resource management implications, variables created in do_some_stuff shouldn't be copied to execute do_some_other_stuff like the lambda version will do.

The second variant makes it more clear what will happen upon invocation.

  1. The do_some_stuff() will be invoked synchronously when you call complex_function.
  2. some_async_operation is called asynchronously and results in a future. The exact moment when this operation is executed depends on your actual asynchronous calling implementation, it might be immediate when you use threads, it might be whenever the .get() is called when you use defered execution.
  3. We don't execute do_some_other_stuff immediately, but rather chain it to the future obtained in step 2. This means that it can be executed as soon as the result from some_async_operation is ready but not before. Aside from that, it's moment of execution is determined by the runtime. If the implementation would just wrap the then proposal, this means it would inherit the parent future's executor/launch policy (as per N3558).
  4. The function returns the last future, that represents the eventual result. Note this NEEDS to be a future, as part of the function body is executed asynchronously.
like image 186
KillianDS Avatar answered Nov 16 '22 21:11

KillianDS


A more complete example (hopefully correct):

future<void> forwardMsgs(istream& streamR, ostream& streamW) async
{
    char buf[512];
    int cnt = 0;
    do {
       cnt = await streamR.read(512, buf);
       if (cnt == 0)
          break;
       cnt = await streamW.write(cnt, buf);
    } while (cnt > 0);
}

future<void> fut = forwardMsgs(myStreamR, myStreamW);

/* do something */

fut.get();

The important point is (quoting from the draft):

After suspending, a resumable function may be resumed by the scheduling logic of the runtime and will eventually complete its logic, at which point it executes a return statement (explicit or implicit) and sets the function’s result value in the placeholder.

and:

A resumable function may continue execution on another thread after resuming following a suspension of its execution.

That is, the thread who originally called forwardMsgs can return at any of the suspension points. If it does, during the /* do something */ line, the code inside forwardMsgs can be executed by another thread even though the function has been called "synchronously".


This example is very similar to

future<void> fut = std::async(forwardMsgs, myStreamR, myStreamW);

/* do something */

fut.get();

The difference is the resumable function can be executed by different threads: a different thread can resume execution (of the resumable function) after each resumption/suspension point.

like image 31
dyp Avatar answered Nov 16 '22 21:11

dyp


I think the idea is that the streamR.read() and streamW.write() calls are asynchronous I/O operations and return futures, which are automatically waited on by the await expressions.

So the equivalent synchronous version would have to call future::get() to obtain the results e.g.

int cnt = 0;
do {
   cnt = streamR.read(512, buf).get();
   if (cnt == 0)
      break;
   cnt = streamW.write(cnt, buf).get();
} while (cnt > 0);

You're correct to point out that there is no concurrency here. However in the context of a resumable function the await makes the behaviour different to the snippet above. When the await is reached the function will return a future, so the caller of the function can proceed without blocking even if the resumable function is blocked at an await while waiting for some other result (e.g. in this case the read() or write() calls to finish.) The resumable function might resume running asynchronously, so the result becomes available in the background while the caller is doing something else.

like image 3
Jonathan Wakely Avatar answered Nov 16 '22 22:11

Jonathan Wakely