Ruby Fiber指南(一)基础

    xiaoxiao2024-06-04  102

    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的参数,参数传递将是下一节的主题。 文章转自庄周梦蝶  ,原文发布时间 2010-03-11      相关资源:敏捷开发V1.0.pptx
    最新回复(0)