Coroutine's Communication
davidl
davidl at 126.com
Mon Apr 28 21:33:40 PDT 2008
For a typical use case of coroutine, a producer/a consumer
var q := new queue
generator produce
loop
while q is not full
create some new items
add the items to q
yield consume
generator consume
loop
while q is not empty
remove some items from q
use the items
yield produce
subroutine dispatcher
var d := new dictionary〈generator → iterator〉
d[produce] := start produce
d[consume] := start consume
var current := produce
loop
current := next d[current]
We can see the typical example from wikipedia uses the global var for
communication. It's bad , because it pollutes global name space, and q can
be accessed by any other threads, thus unexpected behavior may occur.
From the tango implementation:
int s0 = 0;
static int s1 = 0;
Fiber a = new Fiber(
delegate void()
{
s0++;
});
Fiber c = new Fiber(
delegate void() { assert(false); });
There we can see we can wrap those two funcs into a class and also s0,s1.
Yet isn't that trivial to rewrap the coroutine to a class??
Coroutine addresses the cooperativeness of two routines, not their Object
Oriented Programming relation.
I suggest compiler accepts Function type arg as a scope referer.
Use case:
void func(int i)
{
int j;
func1(get_current_scope, i); // which equivalent to j=i;
assert(j==i);
}
void func1(func scope func_scope, ref int j)
{
j = func_scope.j;
}
The trick here is that routines are still routines, and you defaultly get
their local vars consisting an object. Sometimes routines need to access
some trivial
For the simpleness sake , currently we can force the routines can only
access top level local vars which are visible in the whole func scope.
func scope type is a syntax sugar for referencing the local vars of func
by the func_scope stack frame pointer.
This is a useful syntax for optimizing coroutine.
Coroutine performance boosts , because one routine accesses its local var
directly through stack frame pointer.
and this programming paradigm requires get_current_scope primitive for
yielding. Though it can be done by the fiber dispatcher, it can get the
pointer from the context, but it's nicer to have that primitive and yield
that to the dispatcher.
The typical coroutine example can be rewritten as following(with
imaginable syntax):
generator produce
var q := new queue
loop
while q is not full
create some new items
add the items to q
var scope := get_current_scope
yield consume(scope)
generator consume(volatile produce scope produce_scope)
// if yield is sufficient enough to be a memory
// barrier for this specific var produce_scope , we don't need the
volatile.
loop
while produce_scope.q is not empty
remove some items from produce_scope.q
use the items
yield produce
The following is *only* for ***optimization*** purposes:
Even advance compiler technology can optimize this case a bit further.
Since produce doesn't use any local vars of consume. (And this could be
most cases of coroutines.)
And we notice that they only yield to each other. They won't result the
yield chain path any deeper.
So they can be rewritten as two new funcs:
generator produce
var q := new queue
loop
while q is not full
create some new items
add the items to q
var scope := get_current_scope
goto consume.label1
label1:
generator consume:produce(volatile produce scope produce_scope)
loop
while produce_scope.q is not empty
remove some items from produce_scope.q
use the items
goto produce.label1
label1:
It might look confusing at the very first sight. but the point is consume
is never being called as a func.
The semantic of consume:produce is that consume _reuses_ the stack frame
as produce do. Consume only extends the local stack a little bit bigger.
People may ask : Why this is safe?
It's simply because consume works as a nested func of produce.
And you may ask : Why write it in a coroutine syntax?
It's because it provides you the chance to overload both consume/produce.
The only thing they need to extend is their "goto consume.label1"/"goto
produce.label1"
If you want it to have the ability to accept two producers , compiler does
it as following:
produce_label1_address pla;
generator produce
var q := new queue
generator produce1:produce // this semantic means produce1 extends the
produce func stack frame
loop
while q is not full
create some new items
add the items to q
var scope := get_current_scope
goto consume.label1
label1:
generator produce2:produce
loop
while q is not full
create some new items
add the items to q
var scope := get_current_scope
goto consume.label1
label1:
generator consume:produce(volatile produce scope produce_scope)
loop
while produce_scope.q is not empty
remove some items from produce_scope.q
use the items
goto *pla
label1:
compiler assigns pla produce1.label1 to when you do switching between
produce1/consume, and compiler assigns pla produce2.label2 to when you do
switching between produce2/consume, vice versa for consume.
This gives the chance to flatten the coroutines to simple nested funcs,
while without harming any coroutine syntax.
--
使用 Opera 革命性的电子邮件客户程序: http://www.opera.com/mail/
More information about the Digitalmars-d
mailing list