Here is my initial demo of pipes.
Schannels are extremely powerful, but a bit hard to use.
Here,. I'm building a simple pipeline using channels.

The design is part of the chips-and-wires paradigm
for re-usability. In this paradigm programmers can be
delegated the task of implementing reusable components
known as a "chips".

Chips can then be wired together to make circuit boards
(which are of course just new chips).

This technology is used in the electronics industry and provides
a very high level of reusability. It also provides a development
paradigm because chip implementation is context independent.

Chip libraries are similar to function libraries. Chips and functions
have inputs and outputs. There is an important difference though.

Functions are slaves. Composition in weak environments such as
C (and as it turns out LLVM) and probably JVM are severely compromised
by only having subroutine calling available.

Chips, on the other hand, use *exchange* of control, coroutines,
fibres, or whatever you want to call master/master relations.
Chips are active components.

In the pipeline circuit, we have a source, a sink, and a series
of transducers, wired together It is easy "in theory" to do this
without support. In practice it's quite tricky to get the inputs and
outputs right, even for a simple pipeline. The technology below
solves this by providing a familiar concrete representation
of the pipe line idiom.

First, we will make some sample chips.
We make a source which writes integers from 0 to 9 then suicides:

proc source (cho:oschannel[int]) { 
  for var i in 0 upto 9 do write (cho,i); done 
}

And here is a sink, which reads integers and prints them.
It's an infinite loop.

proc sink (chi:ischannel[int]) { 
  while true do var x= read chi; println x; done 
}

Finally a transducer which reads integers,
doubles them, and writes that on its output.

proc xduce (chi: ischannel[int], cho: oschannel[int]) {
  while true do var x = read chi; write (cho, 2 * x); done
}

OK, so now we need two routines to connect these things:
First, we want to wire a source component to a sink.
We return coupled fibre ready to run: a chip with no inputs
or outputs.

fun pipe 
  (w: oschannel[int] -> 0,
  r: ischannel[int] -> 0)
:
  1 -> 0
=> 
  {
    var chi,cho = mk_ioschannel_pair[int] ();
    spawn_fthread { (w cho); };
    spawn_fthread { (r chi); };
  }
;

And here is a function that connects a source to transducer,
to produce a new source:

fun pipe 
  (w: oschannel[int] -> 0,
  t: ischannel[int] * oschannel[int] -> 0)
:
  oschannel[int] -> 0 
=> 
  proc (out:oschannel[int])
  {
    var chi,cho = mk_ioschannel_pair[int] ();
    spawn_fthread { (w cho); };
    spawn_fthread { (t (chi, out)); };
  }
;

Now that's it, for the library, but we need some syntax to make it
easy to use. Felix DSSL's come to the rescue again:

syntax chips {
   // left associative
   x[ssetunion_pri] := x[ssetunion_pri] "||" x[>ssetunion_pri] =># "(infix 
'pipe)"; 
}
open syntax chips;

And now finally we will make a pipeline and execute it:

spawn_fthread$ source || xduce || xduce || sink;

To synchronise with the mainline you can also do this:

#(source || xduce || xduce || sink);

Pipelines constructed this way always work!

It is important not to underestimate how powerful this paradigm is.
Consider:


proc silly (chi: ischannel[int], cho: oschannel[int]) {
  write (cho, 99);
  while true do var x = read chi; write (cho, x); write (cho, 2 * x); done
}

The point? A transducer can read and write whenever it wants.
Its NOT a simple 1-1 relation like a callback function. 
The transducer is a MASTER, not a slave.


HOW it works: only one "chip" is active at once. It yields control
by doing a read or write on a channel.

If there is another fthread which has done a write or read (resp)
one of the two threads gains control again (at present, the
reader so it can copy the data). If there is no matching transfer,
the thread is attached to the channel, and another thread from
the list of waiting threads is resumed.

Note carefully, the waiting fthread is NOT on a wait list
of the scheduler. It hangs off the channel. The channel is
the wait list.

If there are no active threads, the scheduler terminates the
program.

Now the IMPORTANT bit! When properly organised fibres don't
deadlock, they suicide instead. If a channel is unreachable,
the channel will be reaped by the garbage collector. This will
(usually) also kill any fthreads hanging on the channel
(because a channel is nothing more than a list of fibres waiting
on the channel). So an infinite loop is just fine, the fibre will
eventually find there's no data on the channel and no possibility
of any getting there, because no active fthreads own the channel.
So the fibre is deadlocked and subsequently reaped.

CARE must be taken to ensure that some fibre doesn't own
a channel and fail to write to it when required, because the
type system doesn't know whether that fibre is going to write
on the channel or not. If the channel is reachable, fibres
hanging on it remain deadlocked instead of being reaped.
This can hang your program.

CARE also has to be taken not to deadlock by out of order
channel operations.

The importance of the "pipeline" above is that it guarantees
none of these bad things happen. The channels are known
only to the fibres using them.


--
john skaller
skal...@users.sourceforge.net
http://felix-lang.org




------------------------------------------------------------------------------
Keep yourself connected to Go Parallel: 
TUNE You got it built. Now make it sing. Tune shows you how.
http://goparallel.sourceforge.net
_______________________________________________
Felix-language mailing list
Felix-language@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/felix-language

Reply via email to