Support the coroutine design pattern.
A coroutine set is a very simple cooperative non-preemptive
multitasking model, where the switch from one task to another is
performed via an explicit request. Coroutines interact according to
the following rules:
- One coroutine in the set has control, which it retains until it
either exits or resumes another coroutine.
- A coroutine is activated when it is resumed by some other coroutine
for the first time.
- An active coroutine that gives up control by resuming another in
the set retains its context -- including call stack and local variables
-- so that if/when it is resumed, it will proceed from the point at which
it last gave up control.
Coroutines can be thought of as falling somewhere between pipes and
subroutines. Like call/return, there is an explicit flow of control
from one coroutine to another. Like pipes, neither coroutine is
actually "in charge", and neither must exit in order to transfer
control to the other.
One classic application of coroutines is in compilers, where both
the parser and the lexer are maintaining complex state
information. The parser resumes the lexer to process incoming
characters into lexical tokens, and the lexer resumes the parser
when it has reached a point at which it has a reliably interpreted
set of tokens available for semantic processing. Structuring this
as call-and-return would require saving and restoring a
considerable amount of state each time. Structuring it as two tasks
connected by a queue may involve higher overhead (in systems which
can optimize the coroutine metaphor), isn't necessarily as clear in
intent, may have trouble handling cases where data flows in both
directions, and may not handle some of the more complex cases where
more than two coroutines are involved.
Most coroutine systems also provide a way to pass data between the
source and target of a resume operation; this is sometimes referred
to as "yielding" a value. Others rely on the fact that, since only
one member of a coroutine set is running at a time and does not
lose control until it chooses to do so, data structures may be
directly shared between them with only minimal precautions.
"Note: This should not be taken to mean that producer/consumer
problems should be always be done with coroutines." Queueing is
often a better solution when only two threads of execution are
involved and full two-way handshaking is not required. It's a bit
difficult to find short pedagogical examples that require
coroutines for a clear solution.
The fact that only one of a group of coroutines is running at a
time, and the control transfer between them is explicit, simplifies
their possible interactions, and in some implementations permits
them to be implemented more efficiently than general multitasking.
In some situations, coroutines can be compiled out entirely;
in others, they may only require a few instructions more than a
simple function call.
This version is built on top of standard Java threading, since
that's all we have available right now. It's been encapsulated for
code clarity and possible future optimization.
(Two possible approaches: wait-notify based and queue-based. Some
folks think that a one-item queue is a cleaner solution because it's
more abstract -- but since coroutine _is_ an abstraction I'm not really
worried about that; folks should be able to switch this code without
concern.)
%TBD% THIS SHOULD BE AN INTERFACE, to facilitate building other
implementations... perhaps including a true coroutine system
someday, rather than controlled threading. Arguably Coroutine
itself should be an interface much like Runnable, but I think that
can be built on top of this.
|