Chinaunix首页 | 论坛 | 博客
  • 博客访问: 395657
  • 博文数量: 69
  • 博客积分: 1984
  • 博客等级: 上尉
  • 技术积分: 953
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-28 00:43
个人简介

学无所长,一事无成

文章分类

全部博文(69)

文章存档

2015年(19)

2014年(14)

2013年(9)

2012年(17)

2010年(10)

我的朋友

分类: Python/Ruby

2013-10-23 14:43:24

前一天我们学习了 Cellulod 中 async、futuren 的用法。本节学习 signal、link。

一、
signal:

signal 功能强大,稍有不慎容易死锁。基本用法很简单,就 signal 和wait 两个方法,看例子:

  1. require 'celluloid/autostart'

  2. class SignalingExample
  3.   include Celluloid
  4.   attr_reader :signaled
  5.   
  6.   def initialize
  7.     @signaled = false
  8.   end
  9.   
  10.   def wait_for_signal
  11.     value = wait :ponycopter
  12.     @signaled = true
  13.     value
  14.   end
  15.   
  16.   def send_signal(value)
  17.     sleep 5
  18.     signal :ponycopter,value
  19.   end
  20. end

  21. # 添加几行测试代码,演示效果
  22. s1 = SignalingExample.new

  23. future = s1.future.wait_for_signal
  24. s1.async.send_signal(5)

  25. puts 'test signal receiver'
  26. puts future.value
wait 方法用于等待消息,signal 用户发送消息。调用 wait 后,程序挂起,等待消息到达。

二、
Conditions :

直接使用  signal 难度较大,Celluloid 提供了一种更为面向对象的封装 Celluloid::Condition。
对于如何响应异步任务,一般有两种方法:一种为消息通知/订阅者模式,一种为条件变量/消息等待模式。Celluloid::Condition 显然为第二种模式:

  1. require 'rubygems'
  2. require 'celluloid/autostart'

  3. class Foo
  4.   include Celluloid
  5.   
  6.   def execute(x,y,blk)
  7.     sleep 2
  8.     blk.call(x + y)
  9.   end
  10. end

  11. class Bar
  12.   include Celluloid
  13.   
  14.   def test_condition(foo)
  15.     condition = Celluloid::Condition.new
  16.     
  17.     blk = lambda do |sum|
  18.       condition.signal(sum)
  19.     end
  20.     
  21.     foo.async.execute(3,4,blk)
  22.     
  23.     puts "Waiting for foo to complete its execution..."
  24.     wait_result = condition.wait
  25.     puts "wait_result [#{wait_result}]"
  26.     nil
  27.   end
  28. end

  29. f = Foo.new
  30. b = Bar.new

  31. b.test_condition(f)
三、
Notifications :


Notifications 可以让 actor 订阅和发布消息,这些 actor 之间不需要知道注册名,彼此间也不需要关联。


源代码是 ActiveSupport::Notifications 的简化和变形。
在我们的代码中需要 include Celluloid::Notifications,然后我们就可以用 subscribe(topic, method) 和 publish(topic, *payload)订阅和发布消息。 Notifications 将采用异步方式发生给订阅者。

subscribe 方法将返回订阅者对象,随后可用它来取消订阅。ActiveSupport::Notifications 有一个值得注意的特点:整体架构的设计更适合长生命期的消息订阅关系,对于短期消息订阅不太适合。

缺省的通告者(notifier )会向所有订阅者发送消息,但我们可以通过设置 Celluloid::Notifications.notifier 将其替换。Notifier 最好作为一个 actor 使用,其他的一些框架实现会依赖于它(如 dcell 就使用一个 notifier actor 来分发消息)。

缺省的 notifier 会同订阅者间建立 link 管理,这样当订阅者进程崩溃时,订阅关系会被清理掉。

添加代码:require 'celluloid/autostart' ,将会启动一个缺省的 notifier。

四、
上一节提到了 link ,我们来研究一下。
link: 

当  actor 中的任一方法执行出现异常,都会导致 actor crashes 并 die。我们来看个例子:
(这个例子很有趣--用有趣一词有点没心没肺,关乎 James Dean 跟伊丽莎白.泰勒一段往事,先百度一下八卦将有助于程序理解。简单说:James Dean在拍最后一部电影时,出车祸身亡。伊丽莎白.泰勒其时正参演该片,听闻此消息伤心欲绝)。这个例子所要展示的就是异常消息如何传递的。

  1. class JamesDean
  2.   include Celluloid
  3.   class CarInMyLaneError < StandardError; end

  4.   def drive_little_bastard
  5.     raise CarInMyLaneError, "that guy's gotta stop. he'll see us"
  6.   end
  7. end
我们定义一个 JamesDean 类,当其驾车时,触发一个异常,程序于是 crash (正好有车祸的意思) 并 die。

  1. >> james = JamesDean.new
  2.  => #<Celluloid::Actor(JamesDean:0x1068)>
  3. >> james.async.drive_little_bastard
  4.  => nil
  5. >> james
  6.  => #<Celluloid::Actor(JamesDean:0x1068) dead>
此时伊丽莎白.泰勒需要及时知悉此消息,如何做呢。我们需要用 trap_exit 捕获退出异常(并设置一个回调函数),否则这个类也会跟到一起 crash and die 了。

  1. class ElizabethTaylor
  2.   include Celluloid
  3.   trap_exit :actor_died

  4.   def actor_died(actor, reason)
  5.     p "Oh no! #{actor.inspect} has died because of a #{reason.class}"
  6.   end
  7. end
接下来就需要将两者 link 起来了,如下:

  1. >> james = JamesDean.new
  2.  => #<Celluloid::Actor(JamesDean:0x11b8)>
  3. >> elizabeth = ElizabethTaylor.new
  4.  => #<Celluloid::Actor(ElizabethTaylor:0x11f0)>
  5. >> elizabeth.link james
  6.  => #<Celluloid::Actor(JamesDean:0x11b8)>
  7. >> james.async.drive_little_bastard
  8.  => nil
  9. "Oh no! # has died because of a JamesDean::CarInMyLaneError"

如果 actor 中不包含 trap_exit :actor_died ,会有什么后果呢,我们将 JamesDean 类进一步拆分,看下效果:


  1. class PorscheSpider
  2.   include Celluloid
  3.   class CarInMyLaneError < StandardError; end

  4.   def drive_on_route_466
  5.     raise CarInMyLaneError, "head on collision :("
  6.   end
  7. end

  8. class JamesDean
  9.   include Celluloid

  10.   def initialize
  11.     @little_bastard = PorscheSpider.new_link
  12.   end

  13.   def drive_little_bastard
  14.     @little_bastard.drive_on_route_466
  15.   end
  16. end

这里我们又看到了 new_link 方法,他本质上就是将 new 和 link 方法合并成一个,类似如下:

  1. @little_bastard = PorscheSpider.new
  2. current_acotr.link @little_bastard

我们测试一下:

  1. >> james = JamesDean.new
  2.  => #<Celluloid::Actor(JamesDean:0x1108) @little_bastard=#<Celluloid::Actor(PorscheSpider:0x10ec)>>
  3. >> elizabeth = ElizabethTaylor.new
  4.  => #<Celluloid::Actor(ElizabethTaylor:0x1144)>
  5. >> elizabeth.link james
  6.  => #<Celluloid::Actor(JamesDean:0x1108) @little_bastard=#<Celluloid::Actor(PorscheSpider:0x10ec)>>
  7. >> james.async.drive_little_bastard
  8.  => nil
  9. "Oh no! # has died because of a PorscheSpider::CarInMyLaneError"
上述代码我们可以看到是 PorscheSpider 触发了异常,引起 JamesDean 实例 die (没有设置 trap_exit),消息传递到 Elizabeth。
注意:如果我们有一大堆 actor 通过 link 互联,如果没有使用 trap_exit ,则任一 actor crash 将引起所有 acotr 被 kill。
一般来说我们需要设置一个  supervisor 监控分好组的 actors ,在同一组内 actor 彼此相连,当这个组 crash 掉时,我们可以通过 supervisor 进行重启。

如果需要断开 link ,我们可以使用 unlink 方法。


五、Actor lifecycle : actor 的生命期

在 Celluloid 中,每次调用 MyActor.new ,后台都会派生一个本地线程。对于普通的对象,一旦超出作用域,垃圾回收机制将自动清理。但对于 actors ,这种事情不会发生,如果超出作用域,他们只会在后台继续执行。

因此如果要清理 actor ,你需要显式终止。通过调用 terminate 方法,即可正常终止。

  1. actor = MyActor.new
  2. ...
  3. actor.terminate
我们可以将其自动化吗?
通过 link 我们可以管理一组彼此关联的 actors,可以方便的启动终止,而无需逐个操作。  即采用此特性。


阅读(2099) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~