Thread overview | |||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
January 14, 2007 Implementation of C++0x's "future" syntax | ||||
---|---|---|---|---|
| ||||
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."); } } |
January 14, 2007 Re: Implementation of C++0x's "future" syntax | ||||
---|---|---|---|---|
| ||||
Posted in reply to Daniel Keep | Daniel Keep wrote: ... Cool stuff! I'm pretty much a multithreading newbie but it looks nice to me. > 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* Yes very annoying, I had the same in my signal-slots implementation. > * auto meaning = future(meaningOfLife); So true, haha. |
January 14, 2007 Re: Implementation of C++0x's "future" syntax | ||||
---|---|---|---|---|
| ||||
Posted in reply to Daniel Keep | Daniel Keep wrote: > struct FutureResult(tFn, tArgs...) > { [snip] > // 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; > } [snip] > 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; > } So you create a struct on the stack, use a delegate to a member function as a thread entry-point, then return the struct? Does that strike anyone else as a Bad Idea(TM)? The named return value optimization that was recently introduced will probably keep this from breaking in typical code, but if 'result' is ever returned without that optimization being made this seems likely to corrupt stack-based data of new stackframes created afterwards... I _really_ think FutureResult!() should be heap-allocated. If you want, you can even make it a class derived from Thread so no extra allocations need to be made. |
January 15, 2007 Re: Implementation of C++0x's "future" syntax | ||||
---|---|---|---|---|
| ||||
Posted in reply to Daniel Keep | Dang, you beat me to it. I've been working on this for couple of weeks myself. I have an implementation I was thinking of putting up on dsource or somewhere in the next day or two. Its a bit different though, I wrote a thread pool and some other stuff for it. I'll mention something here when I get it posted. Kevin |
January 15, 2007 Re: Implementation of C++0x's "future" syntax | ||||
---|---|---|---|---|
| ||||
Posted in reply to Frits van Bommel | Frits van Bommel wrote:
> I _really_ think FutureResult!() should be heap-allocated. If you want, you can even make it a class derived from Thread so no extra allocations need to be made.
*Bangs head*
Of course, you're absolutely right. *Curses.* I suppose the easiest way to solve it is to turn FutureResult into a class so that it's heap-allocated. You're right I could use a subclass of Thread, but I don't want to expose anything other than the .value/.wait method
Pity, too, since thanks to that optimisation, this would have actually been pretty efficient :3
-- Daniel
|
January 15, 2007 Re: Implementation of C++0x's "future" syntax | ||||
---|---|---|---|---|
| ||||
Posted in reply to Kevin Bealer | Kevin Bealer wrote: > Dang, you beat me to it. Yesss; finally, I beat someone to something :3 > I've been working on this for couple of weeks myself. I have an implementation I > was thinking of putting up on dsource or somewhere in the next day or two. Its a > bit different though, I wrote a thread pool and some other stuff for it. I'll > mention something here when I get it posted. > > Kevin Sounds good. Mine was just a quick little proof of concept. I think that for something like this to be really efficient, it does need a thread pool. Honestly, I've been waiting to see what Tango contains before I go and write my own :P Since I'm going to have to move over to heap allocation (bangs head again), I was wondering: do you use stack allocation, or a custom allocator for small objects? -- Daniel |
January 16, 2007 Re: Implementation of C++0x's "future" syntax | ||||
---|---|---|---|---|
| ||||
Posted in reply to Daniel Keep | == Quote from Daniel Keep (daniel.keep+lists@gmail.com)'s article > Kevin Bealer wrote: > > Dang, you beat me to it. > Yesss; finally, I beat someone to something :3 > > I've been working on this for couple of weeks myself. I have an implementation I was thinking of putting up on dsource or somewhere in the next day or two. Its a bit different though, I wrote a thread pool and some other stuff for it. I'll mention something here when I get it posted. > > > > Kevin > Sounds good. Mine was just a quick little proof of concept. I think > that for something like this to be really efficient, it does need a > thread pool. Honestly, I've been waiting to see what Tango contains > before I go and write my own :P > Since I'm going to have to move over to heap allocation (bangs head > again), I was wondering: do you use stack allocation, or a custom > allocator for small objects? > -- Daniel I had not gotten into the efficiency angle (yet). Right now, the user calls "new" for the 'future' object and it calls new for another 'task' object that represents the work itself. These two classes could be merged to reduce calls to new(). I hadn't thought of this as a critical area since I was thinking that this would be for larger tasks like parsing an input file, sorting large arrays, and so on. I didn't look at your code too deeply but I think your design has some things like capturing the inputs for the delegate, and handling void delegates that mine did not include yet. I have a bug that I want to fix, plus I need to ask my company before releasing the code, but I'm told thats just a formality as long as it doesn't relate to work. Of course there are additional features to consider for later on. Kevin |
January 16, 2007 Re: Implementation of C++0x's "future" syntax | ||||
---|---|---|---|---|
| ||||
Posted in reply to Daniel Keep | Daniel Keep kirjoitti: > > 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: > There are also some interesting possibilities implemented in Fortress: http://research.sun.com/projects/plrg/PLDITutorialSlides9Jun2006.pdf Sun just released it partly under an open source license. Should the next version of D implement some of the asynchronous / parallel functionality as a core language feature or as a library add-on. Well, Bill Baxter already opened a thread about this on digitalmars.D. |
January 16, 2007 Re: Implementation of C++0x's "future" syntax | ||||
---|---|---|---|---|
| ||||
Posted in reply to Daniel Keep | Do you have anyplace to host this? If you want I'll let you put it into "scrapple" on dsource.
Daniel Keep wrote:
>
> 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
>
|
Copyright © 1999-2021 by the D Language Foundation