莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          Ruby Fiber指南(一)基礎(chǔ)

          Posted on 2010-03-11 12:53 dennis 閱讀(8651) 評論(2)  編輯  收藏 所屬分類: 動態(tài)語言my open-source

              Ruby Fiber指南(一)基礎(chǔ)
              Ruby Fiber指南(二)參數(shù)傳遞
              Ruby Fiber指南(三)過濾器
              Ruby Fiber指南(四)迭代器
              Ruby Actor指南(五)實(shí)現(xiàn)Actor
             
              這是一個(gè)Ruby Fiber的教程,基本是按照《Programming in lua》中講述協(xié)程章節(jié)的順序來介紹Ruby Fiber的,初步分為5節(jié):基礎(chǔ)、參數(shù)傳遞、過濾器、迭代器、應(yīng)用。這是第一節(jié),介紹下Ruby Fiber的基礎(chǔ)知識。

              Ruby 1.9引入了Fiber,通常稱為纖程,事實(shí)上跟傳統(tǒng)的coroutine——協(xié)程是一個(gè)概念,一種非搶占式的多線程模型。所謂非搶占式就是當(dāng)一個(gè)協(xié)程運(yùn)行的時(shí)候,你不能在外部終止它,而只能等待這個(gè)協(xié)程主動(一般是yield)讓出執(zhí)行權(quán)給其他協(xié)程,通過協(xié)作來達(dá)到多任務(wù)并發(fā)的目的。協(xié)程的優(yōu)點(diǎn)在于由于全部都是用戶空間內(nèi)的操作,因此它是非常輕量級的,占用的資源很小,并且context的切換效率也非常高效(可以看看這個(gè)測試),在編程模型上能簡化對阻塞操作或者異步調(diào)用的使用,使得涉及到此類操作的代碼變的非常直觀和優(yōu)雅;缺點(diǎn)在于容錯(cuò)和健壯性上需要做更多工作,如果某個(gè)協(xié)程阻塞了,可能導(dǎo)致整個(gè)系統(tǒng)掛住,無法充分利用多核優(yōu)勢,有一定的學(xué)習(xí)使用曲線。
             上面都是場面話,先看看代碼怎么寫吧,比如我們寫一個(gè)打印hello的協(xié)程:
           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庫,事實(shí)上fiber庫并不是必須的,這里是為了調(diào)用Fiber#alive?方法才引入。然后通過Fiber#new創(chuàng)建一個(gè)Fiber,F(xiàn)iber#new接受一個(gè)block,block里就是這個(gè)Fiber將要執(zhí)行的任務(wù)。Fiber#alive?用來判斷Fiber是否存活,一個(gè)Fiber有三種狀態(tài):Created、Running、Terminated,分別表示創(chuàng)建完成、執(zhí)行、終止,處于Created或者Running狀態(tài)的時(shí)候Fiber#alive?都返回true。啟動Fiber是通過Fiber#resume方法,這個(gè)Fiber將進(jìn)入Running狀態(tài),打印"hello"并終止。當(dāng)一個(gè)Fiber終止后,如果你再次調(diào)用resume將拋出異常,告訴你這個(gè)Fiber已經(jīng)壽終正寢了。因此上面的程序輸出是:
          0
          "hello"
          false
          fiber1.rb:
          10:in `resume': dead fiber called (FiberError)
              from fiber1.rb:10:in `<main>'

               眼尖的已經(jīng)注意到了,這里alive?返回是0,而不是true,這是1.9.1這個(gè)版本的一個(gè)BUG,1.9.2返回的就是true。不過在Ruby里,除了nil和false,其他都是true。

              剛才提到,我們?yōu)榱苏{(diào)用Fiber#alive?而引入了fiber庫,F(xiàn)iber其實(shí)是內(nèi)置于語言的,并不需要引入額外的庫,fiber庫對Fiber的功能做了增強(qiáng),具體可以先看看它的文檔,主要是引入了幾個(gè)方法:Fiber#current返回當(dāng)前協(xié)程,F(xiàn)iber#alive?判斷Fiber是否存活,最重要的是Fiber#transfer方法,這個(gè)方法使得Ruby的Fiber支持所謂全對稱協(xié)程(symmetric coroutines),默認(rèn)的resume/yield(yield后面會看到)是半對稱的協(xié)程(asymmetric coroutines),這兩種模型的區(qū)別在于掛起一個(gè)正在執(zhí)行的協(xié)同函數(shù)”與“使一個(gè)被掛起的協(xié)同再次執(zhí)行的函數(shù)”是不是同一個(gè)。在這里就是Fiber#transfer一個(gè)方法做了resume/yield兩個(gè)方法所做的事情。全對稱協(xié)程就可以從一個(gè)協(xié)程切換到任意其他協(xié)程,而半對稱則要通過調(diào)用者來中轉(zhuǎn)。但是Ruby Fiber的調(diào)用不能跨線程(thread,注意跟fiber區(qū)分),只能在同一個(gè)thread內(nèi)進(jìn)行切換,看下面代碼:
          1 = nil
          2 Thread.new do
          3   f = Fiber.new{}
          4 end.join
          5 f.resume

          f在線程內(nèi)創(chuàng)建,在線程外調(diào)用,這樣的調(diào)用在Ruby 1.9里是不允許的,執(zhí)行的結(jié)果將拋出異常
          fiber_thread.rb:5:in `resume': fiber called across threads (FiberError)
              from fiber_thread.rb:5:in `<main>'

              剛才我們僅僅使用了resume,那么yield是干什么的呢?resume是使一個(gè)掛起的協(xié)程執(zhí)行,那么yield就是讓一個(gè)正在執(zhí)行的Fiber掛起并將執(zhí)行權(quán)交給它的調(diào)用者,yield只能在某個(gè)Fiber任務(wù)內(nèi)調(diào)用,不能在root Fiber調(diào)用,程序的主進(jìn)程就是一個(gè)root fiber,如果你在root fiber執(zhí)行一個(gè)Fiber.yield,也將拋出異常:
           Fiber.yield
          FiberError: can
          't yield from root fiber
            
              看一個(gè)resume結(jié)合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是一個(gè)Fiber,它的任務(wù)就是打印1,2,3,第一次調(diào)用resume時(shí),f在打印1之后調(diào)用了Fiber.yield,f將讓出執(zhí)行權(quán)給它的調(diào)用者(這里就是root fiber)并掛起,然后root fiber再次調(diào)用f.resume,那么將從上次掛起的地方繼續(xù)執(zhí)行——打印2,又調(diào)用Fiber.yield再次掛起,最后一次f.resume執(zhí)行后續(xù)的打印任務(wù)并終止f。

              Fiber#yield跟語言中的yield關(guān)鍵字是不同的,block中的yield也有“讓出”的意思,但是這是在同一個(gè)context里,而Fiber#yield讓出就切換到另一個(gè)context去了,這是完全不同的。block的yield其實(shí)是匿名函數(shù)的語法糖衣,它是切換context的,跟Fiber不同的是,它不保留上一次調(diào)用的context,這個(gè)可以通過一個(gè)例子來區(qū)分:
          1 def test
          2    yield
          3    yield
          4    yield
          5 end
          6 test{x ||= 0; puts x+= 1}
          7 
          這里的test方法接受一個(gè)block,三次調(diào)用yield讓block執(zhí)行,block里先是初始化x=0,然后每次調(diào)用加1,你期望打印什么?
          答案是:
          1
          1
          1
          這個(gè)結(jié)果剛好證明了yield是不保留上一次調(diào)用的context,每次x都是重新初始化為0并加上1,因此打印的都是1。讓我們使用Fiber寫同一個(gè)例子:
           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 
          執(zhí)行的結(jié)果是:
          1
          2
          3
          這次能符合預(yù)期地打印1,2,3,說明Fiber的每次掛起都將當(dāng)前的context保存起來,留待下次resume的時(shí)候恢復(fù)執(zhí)行。因此關(guān)鍵字yield是無法實(shí)現(xiàn)Fiber的,fiber其實(shí)跟continuation相關(guān),在底層fiber跟callcc的實(shí)現(xiàn)是一致的(cont.c)。

              Fiber#current返回當(dāng)前執(zhí)行的fiber,如果你在root fiber中調(diào)用Fiber.current返回的就是當(dāng)前的root fiber,一個(gè)小例子:
          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是兩個(gè)不同的Fiber。
              
               基礎(chǔ)的東西基本講完了,最后看看Fiber#transfer的簡單例子,兩個(gè)協(xié)程協(xié)作來打印“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)

          通過這個(gè)例子還可以學(xué)到一點(diǎn),resume可以傳遞參數(shù),參數(shù)將作為Fiber的block的參數(shù),參數(shù)傳遞將是下一節(jié)的主題。


             

          評論

          # re: Ruby Fiber指南(一)基礎(chǔ)  回復(fù)  更多評論   

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

          # re: Ruby Fiber指南(一)基礎(chǔ)  回復(fù)  更多評論   

          2013-12-24 14:01 by daijie
          sorry 沒看清,please remove this comment.
          主站蜘蛛池模板: 牡丹江市| 阿勒泰市| 汾西县| 巴林右旗| 友谊县| 普安县| 上饶市| 淳安县| 班玛县| 桃园市| 定远县| 天柱县| 林西县| 泽州县| 浦城县| 福安市| 尚志市| 卓资县| 神木县| 青神县| 施秉县| 晋江市| 东乌珠穆沁旗| 仪陇县| 宿松县| 深水埗区| 墨竹工卡县| 三原县| 依安县| 肇庆市| 临海市| 全州县| 林口县| 建始县| 白水县| 沧州市| 徐州市| 丹阳市| 青岛市| 茌平县| 淮滨县|