Michael,

I like and agree with some of what you've been saying. I too think that
there's a case of "an x is just a y with ..." underlying the whole
coro/thread/parallel thing. That's why I'm in favor of deconstructing
the threading thing -- a lower thread overhead means more people can
spawn more threads for lower cost.

The coroutine Damian describes is basically, as someone mentioned, a
"thread" running in "non-preemptive, non-prioritized, run-until-yield
mode on a single cpu" -- in other words, the existing concept of
coroutines has been based on the assumption that there is a single
thread of control. The notion of synchronization, of running in a
parallel or multiprocessing environment, is totally not addressed.

So with that in mind, see my enormous proposal from April 15th. I think
that coroutine behavior could be coded with the stuff I proposed, maybe
with a few helper items added in.

Specifically, if we consider that the coroutines are using some kind of
semaphore, then C<yield> has to be a macro (so it can wrap/replace the
C<will do> block of the caller) but the C<do_yield> sub can use the
.active and .result items of the thread. What's more, the .result item,
or some other thread item, could be used to communicate in both
directions, allowing some form of data pass-back. (Probably not
needful, since the wrapper could handle that, too.)

All of which boils down to this: the implementation of "basic"
coroutines is within the scope of the Threads proposal I posted, I<so
long as we understand the semantics>.

So what should the semantics be? Some ideas:

Proposal I:

This has no-yield-from-subs behavior, since the "yield" is only one
level deep.

Further, C<yield> will be a macro which wraps the sub with a shell that
sets up the coro data:

sub do_coro {
  my %hash := Thread::CurrentThread.yield_info;
  if exists %hash{&CALLER.callee} {
    %hash{&CALLER.callee}.(@_)
  } else {
    call;
  }
}

sub do_yield {
  my %hash := Thread::CurrentThread.yield_info;
  call-cc -> { %hash{&CALLER} = shift; }
  leave &CALLER, result => @_;
}

macro yield {
  # Get name of caller from parser
  # Add BEGIN { $caller.wrap &do_coro; } somewhere
  "do_yield";
}

===============================

Proposal II:

This has arbitrary yield depth, allowing nested subs to yield, since
the declaration of the coro stores the return vector:

sub yield {
  call-cc -> { throw Yield, results => @_, resume => shift; }
}

class coro is Subroutine will BEGIN {
  &.do.wrap sub { 
    CATCH { 
      when Yield {
        &resume = &.resume;
        return @.results;
      }
    }

    state &resume;
    my &go = &resume // &call;
    &resume = undef;
    &go @_;
  };
};

=Austin

Reply via email to