Parrot has Continuations. In fact, Parrot is *based* on Continuations: rather than having a call stack and stack frames, each sub call has a return continuation - a pointer back to the call frame and bytecode position to return to after the sub completes.
As the Scheme and Ruby users keep telling us, continuations are pretty neat stuff. In fact they're so neat that they give us light weight cooperative threads "for free".
The basic idea of cooperative threading is reasonably straightforward. There are two basic primitives: spawn and yield. Spawn takes a subroutine reference and schedules it to run as a separate thread. Yield stops the current thread - saving its current state - and starts or resumes the next thread to run. Each thread needs calls to "yield" sprinked throughout, or else the other threads will never get to run.
This type of mechanism is more useful than it sounds. Early versions of both Microsoft Windows and Mac OS used a mechanism like this for *all* of their multitasking.
Going from this to preemptive green threads shouldn't be that big a leap. All it'll need is for the VM to occasionally force a call to "yield".
So here's how we can do this in Parrot with continuations:
# green_threads.pir =head1 Cooperative threads in pure PIR This file demonstrates cooperative concurrency using continuations on Parrot. =cut =head1 th_init First, we create an array to store unscheduled threads in. For simplicity, this is stored in a global. =cut .include 'interpinfo.pasm' .sub th_init $P0 = new 'ResizablePMCArray' set_global 'threads', $P0 .end =head1 th_create This is the 'spawn' call. It takes a sub and schedules it to run as a thread. =cut .sub th_create .param pmc in_sub $P0 = get_global 'threads' push $P0, in_sub .end =head1 th_resched This is the 'yield' call. When called from a running thread, it saves the current point in the computation as a continuation and runs the next thread. =cut .sub th_resched .local pmc cur_th, next_th, ths ths = get_global 'threads' # This gets the return continuation for the currently running sub. # When this continuation is invoked, it will be as if this call # to th_resched just returned. cur_th = interpinfo .INTERPINFO_CURRENT_CONT push ths, cur_th again: next_th = shift ths invokecc next_th $I0 = ths if $I0 > 0 goto again .end =head1 th_main This starts up the thread system (after some threads have been scheduled) and makes sure the program doesn't exit before all the threads have run to completion. =cut .sub th_main .local pmc ths again1: ths = get_global 'threads' $I0 = ths if $I0 <= 0 goto done th_resched() goto again1 done: .end =head1 That's it. All we need now is some test threads and a main function to run them. =cut .sub sub1 say "sub1: Hallo" th_resched() say "sub1: Dance! <(\"< >\")> <(\"<" th_resched() say "sub1: Bye" .end .sub sub2 say "sub2: Hallo" th_resched() say "sub2: Dance! <(\"< >\")> <(\"<" th_resched() say "sub2: Bye" .end .sub sub3 say "sub3: Good morning" th_resched() $I0 = 5 $I1 = $I0 + 3 th_resched() print "sub3: 5 + 3 = " say $I1 th_resched() say "sub3: Is leaving." .end .sub main :main th_init() $P0 = get_global 'sub1' th_create($P0) $P0 = get_global 'sub3' th_create($P0) $P0 = get_global 'sub2' th_create($P0) th_main() say "All done" .end =head1 Expected Output The program should (and does) produce this output: sub1: Hallo sub3: Good morning sub2: Hallo sub1: Dance! <("< >")> <("< sub2: Dance! <("< >")> <("< sub1: Bye sub3: 5 + 3 = 8 sub2: Bye sub3: Is leaving. All done =cut
Here's a link to the file that will eventually break:
http://pandion.ferrus.net/green_threads.pir