Implementation of C++0x's "future" syntax
Daniel Keep
daniel.keep+lists at gmail.com
Sun Jan 14 08:21:46 PST 2007
I was poking around the stuff on C++0x (which I was led to because I was
poking around STL (because I was poking around for a good D container
library)) when I found out about "future".
For those that don't know, the idea is to make asynchronous function
calls nice and easy. The syntax on Wikipedia is this:
> int function( int parameter ) ;
> // Calls the function and immediately returns.
> IdThreadType<function> IdThread = future function(parameter) ;
> // Waits for the function result.
>int ret = wait IdThread ;
Anyway, I had a similar idea a while back, and figured now would be the
time to see if I could do it. So I did. Here's the equivalent in D:
> int func( int parameter ); // "function"'s a keyword :P
> // Calls the function and immediately returns.
> FutureResult!(func) result = future(&func, parameter); // "auto" helps
> // Waits for the function result.
> int ret = result.value;
(It also works for functions that have no return type: you would use
"result.wait" instead.)
Not too shabby considering that C++0x is set to be released in 2009.
That makes us at least two years ahead of the game :)
There are only two improvements I can think of off the top of my head:
1) Investigate whether "future!(func)(args)" is more efficient.
2) Use a thread pool (if the standard library ever gets one).
Enjoy.
-- Daniel
P.S. Kinda OT: has anyone else ever thought that being able to declare
void variables would be *really* handy in templates? I keep having to
put in special cases for void... *grumble, grumble*
future.d:
--------------------
/**
* future -- Simple concurrent execution
* Written by Daniel Keep.
* Released to the Public Domain.
*/
module future;
import std.thread : Thread;
/**
* This structure is what we'll use to store the results of our "future"
* calls in. All that nasty private stuff is basically just bookkeeping
* for the thread we'll spawn off, which allows us to kick off the
* thread, and then store the result when it's finished.
*/
struct FutureResult(tFn, tArgs...)
{
private
{
alias tTaskResult!(tFn) tResult; // result type of future call
tFn fn; // function to call
tArgs args; // arguments to pass to it
bool got_result = false; // have we got a result yet?
Thread thread; // which thread are we using?
static if( !is( tResult == void ) )
tResult result; // what is the result?
// start is used as the entry point to the thread
int start()
{
static if( !is( tResult == void ) )
result = fn(args);
else
fn(args);
got_result = true;
return 0;
}
}
static if( !is( tResult == void ) )
{
/**
* Either returns the value if we already know what it is, or
* else block until we do.
*/
tResult value()
{
while( !got_result )
Thread.yield();
return result;
}
}
else
{
/**
* Wait until the function call has returned.
*/
void wait()
{
while( !got_result )
Thread.yield();
}
}
}
private template tTaskResult(tFn)
{
alias tiTaskResult!(tFn).result tTaskResult;
}
private template tiTaskResult(tFn)
{
static if( is( tFn tRealFn == delegate ) )
alias tiTaskResult!(tRealFn).result result;
else static if( is( tFn tReturn == return ) )
alias tReturn result;
else
static assert("Cannot derive result from function type.");
}
/**
* Performs a function call asynchronously. The "future" refers both
* the fact that the result of this function is intended to be used at
* some time in the future, and that its' basically what the "future"
* keyword in C++0x is supposed to do.
*
* The way it works is quite simple: when you have some lengthy function
* call to make, call the function as early as possible, and store away
* the return. Then, when you actually need the result, you ask for it.
* If the function call completed in the background, it will return the
* result immediately. If it hasn't, then it will block until the
* result is available. For example:
*
* ---------------------------------------------------------------------
* import std.stdio;
* import etc.future;
*
* int meaningOfLife()
* {
* // Some long, complex calculations.
* return 42;
* }
*
* void main()
* {
* auto meaning = future(meaningOfLife);
*
* // More complicated computations.
*
* writefln("Meaning of life is: %d", meaning.value);
* }
* ---------------------------------------------------------------------
*/
FutureResult!(tFn, tArgs) future(tFn, tArgs...)(tFn fn, tArgs args)
{
FutureResult!(tFn, tArgs) result;
result.fn = fn;
foreach( i,a ; args )
result.args[i] = a;
result.thread = new Thread(&result.start);
result.thread.start();
return result;
}
unittest
{
int meaningOfLife(int init)
{
int result = init;
for( int i=1; i<10_000; i++ )
result = (result * i) + (i << 1) - (result >> 1);
// Bah; stuff that!
return 42;
}
void shortPause(int loops)
{
while( loops )
loops--;
}
auto life = future(&meaningOfLife, 0xBeef);
assert( life.value == 42 );
auto pause = future(&shortPause, 1_000);
pause.wait();
}
version( future_main )
{
import std.stdio;
void main()
{
writefln("Tests complete.");
}
}
More information about the Digitalmars-d-announce
mailing list