www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Closure/Stack-to-Heap Semantics

reply dsimcha <dsimcha yahoo.com> writes:
If I write a loop, and want to submit each iteration of that loop to its own
thread, or to a thread pool or something, how do I get D to copy the current
stack frame to the heap each time I use the delegate?  For example:

import std.thread, std.stdio, std.c.time;

void main() {
    Thread[] myThreads = new Thread[100];
    foreach(i; 0..100) {
        auto T = new Thread({
            sleep(1);  //Give i time to be incremented to 100.
            writeln(i);
            return 0;
        });
        T.start;
        myThreads[i] = T;
    }
    foreach(T; myThreads) {
        T.wait;
    }
}

This program prints out all 100's because by the time each thread is done
sleeping, i = 100.  How do I make D copy the stack context each time I submit
a delegate to a thread, in similar fashion to a closure?
Sep 24 2008
next sibling parent Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
dsimcha wrote:
 If I write a loop, and want to submit each iteration of that loop to its own
 thread, or to a thread pool or something, how do I get D to copy the current
 stack frame to the heap each time I use the delegate?  For example:
 

 
 This program prints out all 100's because by the time each thread is done
 sleeping, i = 100.  How do I make D copy the stack context each time I submit
 a delegate to a thread, in similar fashion to a closure?

The only way I know of to get D (presumably v2.x) to copy the stack frame to the heap is to create a delegate using it that would otherwise outlast the stack frame in question. Only one such copy is made per stack frame, however; the solution is therefore to create a separate stack frame per iteration through the loop: ----- import std.thread, std.stdio, std.c.time; int delegate() threadfn(int i) { return { sleep(1); //Give i time to be incremented to 100. writeln(i); return 0; }; } void main() { Thread[] myThreads = new Thread[100]; foreach(i; 0..100) { auto T = new Thread(threadfn(i)); T.start; myThreads[i] = T; } foreach(T; myThreads) { T.wait; } } ----- If you need to access other variables from main() in the threads, either pass a copy (or pointer) to threadfn(), or change threadfn() into a nested function in main. Remember though: any variable not accessed as a copy on threadfn's stackframe will be shared between all the threads.
Sep 25 2008
prev sibling next sibling parent reply downs <default_357-line yahoo.de> writes:
dsimcha wrote:
 If I write a loop, and want to submit each iteration of that loop to its own
 thread, or to a thread pool or something, how do I get D to copy the current
 stack frame to the heap each time I use the delegate?  For example:
 
 import std.thread, std.stdio, std.c.time;
 
 void main() {
     Thread[] myThreads = new Thread[100];
     foreach(i; 0..100) {
         auto T = new Thread({
             sleep(1);  //Give i time to be incremented to 100.
             writeln(i);
             return 0;
         });
         T.start;
         myThreads[i] = T;
     }
     foreach(T; myThreads) {
         T.wait;
     }
 }
 
 This program prints out all 100's because by the time each thread is done
 sleeping, i = 100.  How do I make D copy the stack context each time I submit
 a delegate to a thread, in similar fashion to a closure?

This is how I'd do it with tools/1.0: void main() { auto tp = new Threadpool(100); auto sem = new Semaphore; foreach (i; Range[0..100].endIncl) { tp.addTask(i /apply/ (int i) { sleep(1); writefln(i); sem.release; }); } foreach (i; Range[0..100].endIncl) sem.acquire; } (Untested)
Sep 25 2008
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
downs:
   foreach (i; Range[0..100].endIncl) {

That's a bit ugly. Ruby uses ... to denote an interval closed on the right and .. to denote an interval open on the right. But it's easy to miss the difference when you read code, so that too is bad design. Python always uses intervals open on the right, avoiding problems. D slices and D2 foreach interval syntax too use the same semantics. In my d libs I use something more readable: foreach (i; xrange(100+1)) { If you really really want to iterate on an interval closed on the right you can use an iterable with a different name, this helps the person that reads your code spot the difference quickly, for example: foreach (i; xinterval(100)) { Bye, bearophile
Sep 25 2008
prev sibling parent "Bill Baxter" <wbaxter gmail.com> writes:
On Thu, Sep 25, 2008 at 10:04 PM, bearophile <bearophileHUGS lycos.com> wrote:
 downs:
   foreach (i; Range[0..100].endIncl) {

That's a bit ugly. Ruby uses ... to denote an interval closed on the right and .. to denote an interval open on the right. But it's easy to miss the difference when you read code, so that too is bad design.

That would make a lot of sense if it is what Ruby actually did, but unfortunately the actual roles of ... and .. are reversed from what you just said. E.g. from here: http://www.techotopia.com/index.php/Ruby_Ranges """ 1..10 # Creates a range from 1 to 10 inclusive 1...10 # Creates a range from 1 to 9 """ And it's also true that it's probably too easy to overlook the difference. --bb
Sep 25 2008
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
dsimcha wrote:
 If I write a loop, and want to submit each iteration of that loop to its own
 thread, or to a thread pool or something, how do I get D to copy the current
 stack frame to the heap each time I use the delegate?  For example:
 
 import std.thread, std.stdio, std.c.time;
 
 void main() {
     Thread[] myThreads = new Thread[100];
     foreach(i; 0..100) {
         auto T = new Thread({
             sleep(1);  //Give i time to be incremented to 100.
             writeln(i);
             return 0;
         });
         T.start;
         myThreads[i] = T;
     }
     foreach(T; myThreads) {
         T.wait;
     }
 }
 
 This program prints out all 100's because by the time each thread is done
 sleeping, i = 100.  How do I make D copy the stack context each time I submit
 a delegate to a thread, in similar fashion to a closure?

Your code is correct and should work as you expect. It doesn't only because of a bug: http://d.puremagic.com/issues/show_bug.cgi?id=2043 The workaround is to envelop the foreach body in a function, as explained in the report. Strangely, you'll run into another bug: writefln is not thread-safe!? You will have to envelop it with a synchronized statement. So the code looks like this: foreach(i; 0..100) { (int i) { writeln("(BEGIN) i: ", i, " &i: ", &i); auto T = new Thread({ sleep(1); //Give i time to be incremented to 100. synchronized { writeln("i: ", i, " &i: ", &i); } return 0; }); T.start; myThreads[i] = T; } (i); } -- Bruno Medeiros - Software Developer, MSc. in CS/E graduate http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Oct 03 2008