View mode: basic / threaded / horizontal-split · Log in · Help
September 25, 2008
Closure/Stack-to-Heap Semantics
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?
September 25, 2008
Re: Closure/Stack-to-Heap Semantics
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:
> 
[snip]
> 
> 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.
September 25, 2008
Re: Closure/Stack-to-Heap Semantics
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)
September 25, 2008
Re: Closure/Stack-to-Heap Semantics
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
September 25, 2008
Re: Closure/Stack-to-Heap Semantics
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
October 03, 2008
Re: Closure/Stack-to-Heap Semantics
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
Top | Discussion index | About this forum | D home