Fiber in Ruby 1.9

28 Dec 2009

Ruby 1.9新推出了Fiber这个新的概念,有人说它是轻量级的Thread,其实不然。它是一段代码块,可以停止、继续,可以有返回值、写入值,有多个Fiber时,它们的执行顺序是固定。它和Thread相似的是,它的执行不是线性的,它可以在中途停止,将控制权交给主程序或者是其它的Fiber,但是中控制权交接的过程是由你来控制的,而不是线程调度程序。所以有时候Fiber可以完成之前只能用Thread才能完成的任务(比如:Producer-Consumer)。

先来看看一个例子吧:

require 'fiber'

fiber = Fiber.new do
  (1..3).each do |i|
    Fiber.yield(i)
  end
end

while fiber.alive?
  puts fiber.resume
end

运行结果是

1
2
3
1..3

首先,Fiber.new定义了一个Fiber,但是并不会执行,直到调用这个Fiber的resume方法,这个Fiber才会执行,并且当执行到Fiber.yield时停止,并且把yield的参数返回给主程序,同时将控制权将给主程序。然后主程序继续执行,调用这个Fiber的resume方法,这个Fiber从刚才停止的地方继续执行,直到Fiber.yield,以此类推。当这个Fiber执行完毕时,Fiber#alive?返回false,如果这个时候继续调用Fiber#resume,系统将抛出异常。

运行结果稍微与我们的预想有点偏差,我们并不需要最后一行1..3,原因是在调用三次Fiber#resume分别返回1、2、3,这个时候Fiber并没有执行完毕,所以Fiber#alive?仍然返回true,第四次调用Fiber#resume返回的这个Fiber block内的返回值,这里就是(1..3),所以我们需要对程序做点小小的修改

require 'fiber'

fiber = Fiber.new do
  (1..3).each do |i|
    Fiber.yield(i)
  end
end

loop do
  output = fiber.resume
  break unless fiber.alive?
  puts output
end

这下运行的结果就和预期一致了。

再来看看如何向Fiber写入数据

require 'fiber'

fiber = Fiber.new do
  loop do
    input = Fiber.yield
    break if input.to_s.empty?
    puts input
  end
end

(1..3).each do |i|
  fiber.resume i
end

运行结果

2
3

这和从Fiber中读取数据是个相反的操作,通过给Fiber#resume传递参数将数据作为Fiber.yield的返回值传入Fiber。

你可能会很奇怪,为什么只打印了2和3,没有1呢?因为在第一次调用Fiber#resume的时候,Fiber还没有开始,resume的参数是传递给Fiber block的,当这个Fiber运行到Fiber.yield时,这个Fiber停止,然后第二次调用Fiber#resume的时候,将2传递了Fiber,并作为yield的返回值,所以打印了2,我们需要修改一下代码来打印1、2、3

require 'fiber'

fiber = Fiber.new do |title|
  puts title
  loop do
    input = Fiber.yield
    break if input.to_s.empty?
    puts input
  end
end

fiber.resume 'title'
(1..3).each do |i|
  fiber.resume i
end

运行结果为

title
1
2
3

如果需要在两个Fiber之间切换的话,可以使用Fiber#transfer,用来实现Producer-Consumer。

Tags 


blog comments powered by Disqus