庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

Ruby Fiber指南(一)基础

Posted on 2010-03-11 12:53 dennis 阅读(7768) 评论(2)  编辑  收藏 所属分类: 动态语言my open-source

    Ruby Fiber指南(一)基础
    Ruby Fiber指南(二)参数传递
    Ruby Fiber指南(三)过滤器
    Ruby Fiber指南(四)迭代器
    Ruby Actor指南(五)实现Actor
   
    这是一个Ruby Fiber的教程,基本是按照《Programming in lua》中讲述协程章节的顺序来介绍Ruby Fiber的,初步分为5节:基础、参数传递、过滤器、迭代器、应用。这是第一节,介绍下Ruby Fiber的基础知识。

    Ruby 1.9引入了Fiber,通常称为纤程,事实上跟传统的coroutine——协程是一个概念,一种非抢占式的多线程模型。所谓非抢占式就是当一个协程运行的时候,你不能在外部终止它,而只能等待这个协程主动(一般是yield)让出执行权给其他协程,通过协作来达到多任务并发的目的。协程的优点在于由于全部都是用户空间内的操作,因此它是非常轻量级的,占用的资源很小,并且context的切换效率也非常高效(可以看看这个测试),在编程模型上能简化对阻塞操作或者异步调用的使用,使得涉及到此类操作的代码变的非常直观和优雅;缺点在于容错和健壮性上需要做更多工作,如果某个协程阻塞了,可能导致整个系统挂住,无法充分利用多核优势,有一定的学习使用曲线。
   上面都是场面话,先看看代码怎么写吧,比如我们写一个打印hello的协程:
 1 require 'fiber'
 2 f=Fiber.new do
 3   p "hello"
 4 end
 5 
 6 p f.alive?
 7 f.resume
 8 p f.alive?
 9 
10 f.resume
11
    附注:这里的代码都在ruby1.9.1-p378测试通过。

     第一行先引入fiber库,事实上fiber库并不是必须的,这里是为了调用Fiber#alive?方法才引入。然后通过Fiber#new创建一个Fiber,Fiber#new接受一个block,block里就是这个Fiber将要执行的任务。Fiber#alive?用来判断Fiber是否存活,一个Fiber有三种状态:Created、Running、Terminated,分别表示创建完成、执行、终止,处于Created或者Running状态的时候Fiber#alive?都返回true。启动Fiber是通过Fiber#resume方法,这个Fiber将进入Running状态,打印"hello"并终止。当一个Fiber终止后,如果你再次调用resume将抛出异常,告诉你这个Fiber已经寿终正寝了。因此上面的程序输出是:
0
"hello"
false
fiber1.rb:
10:in `resume': dead fiber called (FiberError)
    from fiber1.rb:10:in `<main>'

     眼尖的已经注意到了,这里alive?返回是0,而不是true,这是1.9.1这个版本的一个BUG,1.9.2返回的就是true。不过在Ruby里,除了nil和false,其他都是true。

    刚才提到,我们为了调用Fiber#alive?而引入了fiber库,Fiber其实是内置于语言的,并不需要引入额外的库,fiber库对Fiber的功能做了增强,具体可以先看看它的文档,主要是引入了几个方法:Fiber#current返回当前协程,Fiber#alive?判断Fiber是否存活,最重要的是Fiber#transfer方法,这个方法使得Ruby的Fiber支持所谓全对称协程(symmetric coroutines),默认的resume/yield(yield后面会看到)是半对称的协程(asymmetric coroutines),这两种模型的区别在于挂起一个正在执行的协同函数”与“使一个被挂起的协同再次执行的函数”是不是同一个。在这里就是Fiber#transfer一个方法做了resume/yield两个方法所做的事情。全对称协程就可以从一个协程切换到任意其他协程,而半对称则要通过调用者来中转。但是Ruby Fiber的调用不能跨线程(thread,注意跟fiber区分),只能在同一个thread内进行切换,看下面代码:
1 = nil
2 Thread.new do
3   f = Fiber.new{}
4 end.join
5 f.resume

f在线程内创建,在线程外调用,这样的调用在Ruby 1.9里是不允许的,执行的结果将抛出异常
fiber_thread.rb:5:in `resume': fiber called across threads (FiberError)
    from fiber_thread.rb:5:in `<main>'

    刚才我们仅仅使用了resume,那么yield是干什么的呢?resume是使一个挂起的协程执行,那么yield就是让一个正在执行的Fiber挂起并将执行权交给它的调用者,yield只能在某个Fiber任务内调用,不能在root Fiber调用,程序的主进程就是一个root fiber,如果你在root fiber执行一个Fiber.yield,也将抛出异常:
 Fiber.yield
FiberError: can
't yield from root fiber
  
    看一个resume结合yield的例子:
 1 f=Fiber.new do
 2   p 1
 3   Fiber.yield
 4   p 2
 5   Fiber.yield
 6   p 3
 7 end
 8 
 9 f.resume # =>打印1
10 f.resume # => 打印2
11 f.resume # =>打印3

   f是一个Fiber,它的任务就是打印1,2,3,第一次调用resume时,f在打印1之后调用了Fiber.yield,f将让出执行权给它的调用者(这里就是root fiber)并挂起,然后root fiber再次调用f.resume,那么将从上次挂起的地方继续执行——打印2,又调用Fiber.yield再次挂起,最后一次f.resume执行后续的打印任务并终止f。

    Fiber#yield跟语言中的yield关键字是不同的,block中的yield也有“让出”的意思,但是这是在同一个context里,而Fiber#yield让出就切换到另一个context去了,这是完全不同的。block的yield其实是匿名函数的语法糖衣,它是切换context的,跟Fiber不同的是,它不保留上一次调用的context,这个可以通过一个例子来区分:
1 def test
2    yield
3    yield
4    yield
5 end
6 test{x ||= 0; puts x+= 1}
7 
这里的test方法接受一个block,三次调用yield让block执行,block里先是初始化x=0,然后每次调用加1,你期望打印什么?
答案是:
1
1
1
这个结果刚好证明了yield是不保留上一次调用的context,每次x都是重新初始化为0并加上1,因此打印的都是1。让我们使用Fiber写同一个例子:
 1 fiber=Fiber.new do
 2    x||=0
 3    puts x+=1
 4    Fiber.yield
 5    puts x+=1
 6    Fiber.yield
 7    puts x+=1
 8    Fiber.yield
 9 end
10 
11 fiber.resume
12 fiber.resume
13 fiber.resume
14 
执行的结果是:
1
2
3
这次能符合预期地打印1,2,3,说明Fiber的每次挂起都将当前的context保存起来,留待下次resume的时候恢复执行。因此关键字yield是无法实现Fiber的,fiber其实跟continuation相关,在底层fiber跟callcc的实现是一致的(cont.c)。

    Fiber#current返回当前执行的fiber,如果你在root fiber中调用Fiber.current返回的就是当前的root fiber,一个小例子:
1 require 'fiber'
2 f=Fiber.new do
3    p Fiber.current
4 end
5 
6 p Fiber.current
7 f.resume

这是一次输出:
#<Fiber:0x9bf89f4>
#
<Fiber:0x9bf8a2c>
表明root fiber跟f是两个不同的Fiber。
    
     基础的东西基本讲完了,最后看看Fiber#transfer的简单例子,两个协程协作来打印“hello world”:
 1 require 'fiber'
 2 
 3 f1=Fiber.new do |other|
 4     print "hello"
 5     other.transfer
 6 end
 7 
 8 f2=Fiber.new do
 9     print " world\n"
10 end
11 
12 f1.resume(f2)

通过这个例子还可以学到一点,resume可以传递参数,参数将作为Fiber的block的参数,参数传递将是下一节的主题。


   

评论

# re: Ruby Fiber指南(一)基础  回复  更多评论   

2013-12-24 14:00 by daijie
Fibur = Thread ?

# re: Ruby Fiber指南(一)基础  回复  更多评论   

2013-12-24 14:01 by daijie
sorry 没看清,please remove this comment.

只有注册用户登录后才能发表评论。


网站导航: