Ruby Fiber指南(一)基础

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 f = 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}

这里的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

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 
   

时间: 2024-12-30 16:16:22

Ruby Fiber指南(一)基础的相关文章

Ruby Fiber指南(二)参数传递

  Ruby Fiber指南(一)基础     Ruby Fiber指南(二)参数传递     Ruby Fiber指南(三)过滤器    Ruby Fiber指南(四)迭代器     Ruby Actor指南(五)实现Actor     这一篇其实也算是Fiber编程的基础篇,只不过参数传递算是一个比较重要的主题,因此独立一节.参数传递发生在两个Fiber之间,作为Fiber之间通讯的一个主要手段.     首先,我们可以通过resume调用给Fiber的block传递参数: 1 #resum

Ruby Fiber指南(四)迭代器

    Ruby Fiber指南(一)基础     Ruby Fiber指南(二)参数传递     Ruby Fiber指南(三)过滤器    Ruby Fiber指南(四)迭代器     Ruby Actor指南(五)实现Actor      上一节介绍了利用Fiber实现类unix管道风格的过滤链,这一节将介绍利用Fiber来实现迭代器,我们可以将循环的迭代器看作生产者-消费者模式的特殊的例子.迭代函数产生值给循环体消费.所以可以使用Fiber来实现迭代器.协程的一个关键特征是它可以不断颠倒

Ruby Fiber指南(三)过滤器

Ruby Fiber指南(一)基础     Ruby Fiber指南(二)参数传递     Ruby Fiber指南(三)过滤器    Ruby Fiber指南(四)迭代器     Ruby Actor指南(五)实现Actor      在学习了Fiber的基础知识之后,可以尝试用Fiber去做一些比较有趣的事情.这一节将讲述如何使用Fiber来实现类似unix系统中的管道功能.在unix系统中,可以通过管道将多个命令组合起来做一些强大的功能,最常用的例如查找所有的java进程: ps aux|

Lua、LuaJIT Coroutine和Ruby Fiber的切换效率对比

最近重读了<Programming Lua>,对协程做了重点复习.众所周知,Ruby1.9引入了Fiber,同样是coroutine,不过Ruby Fiber支持全对称协程(通过fiber库),而Lua只支持所谓半对称协程.     这里将对Lua.LuaJIT和Ruby Fiber的切换效率做个对比测试,测试场景很简单:两个coroutine相互切换达到5000万次,统计每秒切换的次数,各测试多次取最佳.     lua的程序如下:     c1=coroutine.create(funct

JavaScript的RequireJS库入门指南_基础知识

 简介 如今最常用的JavaScript库之一是RequireJS.最近我参与的每个项目,都用到了RequireJS,或者是我向它们推荐了增加RequireJS.在这篇文章中,我将描述RequireJS是什么,以及它的一些基础场景. 异步模块定义(AMD) 谈起RequireJS,你无法绕过提及JavaScript模块是什么,以及AMD是什么. JavaScript模块只是遵循SRP(Single Responsibility Principle单一职责原则)的代码段,它暴露了一个公开的API.

Javascript学习指南_基础知识

javascript入门太容易了,导致几乎人人随便看看就能上手,零基础的人学个三五天都能对外宣称自己掌握了js.可是真正掌握js是一件很难的事情.如果在初学一门语言的时候第一想到的是问别人,是很难取得进步的.因为得到答案太容易,而不会去想为什么.而且说实话,js并不适合作为第一门编程语言,它兼容并包,容错性高,但这也意味着一但出错你就很难找到错误的原因.另一方面js有一些优秀的特性对于没有编程经历的人来说也很难发现. 如果你真想学好js,我有个大概的自学轨迹供你们参考. 第一步,学习w3scho

JavaScript的函数式编程基础指南_基础知识

引言 JavaScript是一种强大的,却被误解的编程语言.一些人喜欢说它是一个面向对象的编程语言,或者它是一个函数式编程语言.另外一些人喜欢说,它不是一个面向对象的编程语言,或者它不是一个函数式编程语言.还有人认为它兼具面向对象语言和函数式语言的特点,或者,认为它既不是面向对象的也不是函数式的,好吧,让我们先搁置那些争论. 让我们假设我们共有这样的一个使命:在JavaScript语言所允许的范围内,尽可能多的使用函数式编程的原则来编写程序. 首先,我们需要清理下脑子里那些关于函数式编程的错误观

再JavaScript的jQuery库中编写动画效果的指南_基础知识

jquery中常用的动画的方法就是hide()与show(). $(element).hide()这段代码可以与这相等element.css("display","none")  在hide(time)与show(time)中填入事件,可以慢慢消失跟显现.可以修改元素的多个样式,高度,宽度,不透明度. 另一组方法fadeIn()与fadeOut()这个与hide跟show不同的是,当使用hide或者show的时候会改变网页的高度,而fadeIn与fadeOut则不会

谷歌设计师出品的VR设计指南之基础概念与设计工具

  在本文的第二部分当中,我们来了解一些作为设计师需要掌握的VR基础概念及相关设计工具.我们不会探索的过于深入,面向设计师的职能点到为止即可. 新的空间维度与沉浸式体验所带来的冲击是前所未有的.要在三维世界当中打造令人舒适的互动体验,你需要了解一些此前可能并未接触过的设计与技术原则.我们的Cardboard团队将一系列概念知识打包到了Cardboard Design Lab这款app(Android)当中,你可以配合Cardboard沉浸到VR世界当中进行学习. 此外还要记得观看Alex在201