前一天我们学习了 Cellulod 中 async、futuren 的用法。本节学习 signal、link。
一、
signal:
signal 功能强大,稍有不慎容易死锁。基本用法很简单,就 signal 和wait 两个方法,看例子:
-
require 'celluloid/autostart'
-
-
class SignalingExample
-
include Celluloid
-
attr_reader :signaled
-
-
def initialize
-
@signaled = false
-
end
-
-
def wait_for_signal
-
value = wait :ponycopter
-
@signaled = true
-
value
-
end
-
-
def send_signal(value)
-
sleep 5
-
signal :ponycopter,value
-
end
-
end
-
-
# 添加几行测试代码,演示效果
-
s1 = SignalingExample.new
-
-
future = s1.future.wait_for_signal
-
s1.async.send_signal(5)
-
-
puts 'test signal receiver'
-
puts future.value
wait 方法用于等待消息,signal 用户发送消息。调用 wait 后,程序挂起,等待消息到达。
二、
Conditions :
直接使用 signal 难度较大,Celluloid 提供了一种更为面向对象的封装 Celluloid::Condition。
对于如何响应异步任务,一般有两种方法:一种为消息通知/订阅者模式,一种为条件变量/消息等待模式。Celluloid::Condition 显然为第二种模式:
-
require 'rubygems'
-
require 'celluloid/autostart'
-
-
class Foo
-
include Celluloid
-
-
def execute(x,y,blk)
-
sleep 2
-
blk.call(x + y)
-
end
-
end
-
-
class Bar
-
include Celluloid
-
-
def test_condition(foo)
-
condition = Celluloid::Condition.new
-
-
blk = lambda do |sum|
-
condition.signal(sum)
-
end
-
-
foo.async.execute(3,4,blk)
-
-
puts "Waiting for foo to complete its execution..."
-
wait_result = condition.wait
-
puts "wait_result [#{wait_result}]"
-
nil
-
end
-
end
-
-
f = Foo.new
-
b = Bar.new
-
-
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在拍最后一部电影时,出车祸身亡。伊丽莎白.泰勒其时正参演该片,听闻此消息伤心欲绝)。这个例子所要展示的就是异常消息如何传递的。
-
class JamesDean
-
include Celluloid
-
class CarInMyLaneError < StandardError; end
-
-
def drive_little_bastard
-
raise CarInMyLaneError, "that guy's gotta stop. he'll see us"
-
end
-
end
我们定义一个 JamesDean 类,当其驾车时,触发一个异常,程序于是 crash (正好有车祸的意思) 并 die。
-
>> james = JamesDean.new
-
=> #<Celluloid::Actor(JamesDean:0x1068)>
-
>> james.async.drive_little_bastard
-
=> nil
-
>> james
-
=> #<Celluloid::Actor(JamesDean:0x1068) dead>
此时伊丽莎白.泰勒需要及时知悉此消息,如何做呢。我们需要用 trap_exit 捕获退出异常(并设置一个回调函数),否则这个类也会跟到一起 crash and die 了。
-
class ElizabethTaylor
-
include Celluloid
-
trap_exit :actor_died
-
-
def actor_died(actor, reason)
-
p "Oh no! #{actor.inspect} has died because of a #{reason.class}"
-
end
-
end
接下来就需要将两者 link 起来了,如下:
-
>> james = JamesDean.new
-
=> #<Celluloid::Actor(JamesDean:0x11b8)>
-
>> elizabeth = ElizabethTaylor.new
-
=> #<Celluloid::Actor(ElizabethTaylor:0x11f0)>
-
>> elizabeth.link james
-
=> #<Celluloid::Actor(JamesDean:0x11b8)>
-
>> james.async.drive_little_bastard
-
=> nil
-
"Oh no! # has died because of a JamesDean::CarInMyLaneError"
如果 actor 中不包含 trap_exit :actor_died ,会有什么后果呢,我们将 JamesDean 类进一步拆分,看下效果:
-
class PorscheSpider
-
include Celluloid
-
class CarInMyLaneError < StandardError; end
-
-
def drive_on_route_466
-
raise CarInMyLaneError, "head on collision :("
-
end
-
end
-
-
class JamesDean
-
include Celluloid
-
-
def initialize
-
@little_bastard = PorscheSpider.new_link
-
end
-
-
def drive_little_bastard
-
@little_bastard.drive_on_route_466
-
end
-
end
这里我们又看到了 new_link 方法,他本质上就是将 new 和 link 方法合并成一个,类似如下:
-
@little_bastard = PorscheSpider.new
-
current_acotr.link @little_bastard
我们测试一下:
-
>> james = JamesDean.new
-
=> #<Celluloid::Actor(JamesDean:0x1108) @little_bastard=#<Celluloid::Actor(PorscheSpider:0x10ec)>>
-
>> elizabeth = ElizabethTaylor.new
-
=> #<Celluloid::Actor(ElizabethTaylor:0x1144)>
-
>> elizabeth.link james
-
=> #<Celluloid::Actor(JamesDean:0x1108) @little_bastard=#<Celluloid::Actor(PorscheSpider:0x10ec)>>
-
>> james.async.drive_little_bastard
-
=> nil
-
"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 方法,即可正常终止。
-
actor = MyActor.new
-
...
-
actor.terminate
我们可以将其自动化吗?
通过 link 我们可以管理一组彼此关联的 actors,可以方便的启动终止,而无需逐个操作。
即采用此特性。
阅读(2105) | 评论(0) | 转发(0) |