7.6.2010 22:15, pesco
tags: code ruby fibers callcc
Actually, stripped of comments and stuff, it's even less than 42 lines.
Fibers
are a big new thing with Ruby 1.9. The name is supposed to suggest
that they're the thinner things inside a thread.
You create them with a block to execute but they won't run in parallel.
Instead they are explicitly entered and left via
resume and
yield.
The first call to
resume enters at the top of the block.
Calling
yield from inside the block jumps back out and the next
resume
jumps back inside. Repeat as often as you like.
f = Fiber.new {
...
Fiber.yield 23 # returns 5
...
}
f.resume # start it up; returns 23
... # control transfers back here after "yield"
f.resume 5 # run the rest
The two can pass arguments to each other like regular procedure calls.
So it's indeed like threads as there are independent control flows.
But execution switches only explicitly and intercommunication is much more
direct.
Today I found out what they are really useful for:
To separate some very sequential business logic (do A, do B, do C, finish)
from the event-centric tangle of a GUI toolkit.
A fiber does A, B, and C in sequence.
Where
does
means it starts the job in the background,
registers an event handler,
and immediately yields back to the GUI event loop.
When the job finishes,
the handler resumes the fiber and it goes on to the next step.
Ruby 1.9 adds fibers as a primitive, but they are also easily implemented
in terms of
call/cc -
call with current continuation
, which Ruby has had for no idea how long.
So here goes, a drop-in substitute for 1.9's
Fiber class:
class Fibr
@@fs = [] # a stack of fibers corresponding to calls of 'resume'
def initialize(&block)
@k = lambda(&block) # lambda makes 'return' work as expected
end
def resume(*xs)
@@fs.push(self)
jump(xs) # jumping into fiber
end
def self.current
@@fs.last
end
def self.yield(*xs)
f = @@fs.pop
f && f.send(:jump, xs) # jumping out of fiber
end
private
def jump(xs)
callcc { |k|
destination = @k
@k = k
destination.call(*xs)
@@fs.pop
@k.call # return from the last 'resume'
}
end
end
Fiber = Fibr if RUBY_VERSION<"1.9"
Download:
fibr.rb