Thursday, June 26, 2014

Future/Promise Discussion

Introduction

Currently, C++11 has introduced async and future/promise pattern. It allows performing concurrent operations and waiting for the result. It looks very promising (in future). Yep.

I don't want to discuss the current standard implementation. It looks like an initial step and contains several flaws (destructor behavior, no thread pools, only blocking semantic for value retrieval etc). I would like to discuss particular usage and overhead questions.

There are several typical usage:

  1. Start the task asynchronously and wait for the result.
  2. Start the task asynchronously and don't wait for the result.
  3. Start several (or a lot) tasks asynchronously and wait for the results.

Let's discuss them in details.

Start the task asynchronously and wait for the result

The idea is simple: sometimes I need start task asynchronously while continuing doing processing at the same time. If the result of the task is needed I invoke the method get() from the future to obtain the result. It looks pretty simple. The only thing is that this is the only case where future/promise technique is very well suited.

Let's consider another typical usage.

Start the task asynchronously and don't wait for the result

The idea is even simpler: I don't want for the result. I just want to start some action to perform operation. Nothing more. Is it possible to implement this case using standard library? No!

But I would like to concentrate on another aspect. Let's suppose that we have detach() method. OK. What do we have? We have mutex, critical section and other interesting stuff to correctly operate with shared state. But we don't want to have the shared state! Why should I pay for it? Okay.

Let's continue.

Start several (or a lot) tasks asynchronously and wait for the results

So what should I do? I need to create a vector of futures, put all futures inside vector, iterate through them and invoke get() or wait() methods. If I'm lucky there is no context switches take place. Really? No!

At each get() invocation we either have a result (so only mutex.lock()/unlock() is called or atomic flag depending on the implementation) or we should wait on condition variable. So in worst case each iteration requires context switch. And this worst case is the common case! Because usually the amount of task work is much more then work needed to iterate through futures (obviously).

And what should I do?

There is a solution! Just use appropriate implementations for different cases. See my implementation as an example:

Below one may find some explanations how to use it:

  1. If you want to invoke the task asynchronously and wait for result, use goWait or Waiter.
  2. If you want to invoke the task asynchronously and don't wait for result, use go.
  3. If you want to invoke several tasks asynchronously and wait for results, use goWait or Waiter. So the library implements several tasks in the same way as described in item #1
  4. If you want to invoke several tasks asynchronously and wait for the first actual result, use goAnyWait or goAnyResult.

Try it!

No comments :

Post a Comment