我在Ruby1.9.2下运行这个代码片段:
require "eventmachine"
require "fiber"
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
EM.add_timer(2) do
print "B"
current_fiber.resume("D")
end
Fiber.yield
end
print "A"
val = fiber.resume
print "C"
print val
EM.stop
end
我希望输出为“abcd”,程序在“a”之后暂停两秒钟。然而,它只是立即打印出“AC”,然后等待两秒钟后退出。我做错什么了?
(作为参考,我试图在不使用em synchrony的情况下重现this article中描述的em synchrony风格的行为。)
编辑:这里有一些我最终想要完成的细节。我正在开发一个运行在thin上的grape api,在返回响应之前,每个路由处理程序必须对数据存储、zookeeper、其他http服务等进行各种调用。
em-synchrony确实很酷,但我一直遇到根纤维屈服的问题,或者结果显示了上述情况的非同步症状。rack-fiber_pool似乎也很有用,但我不愿意承诺使用它,因为它打破了所有的rack::测试单元测试。
我将问题简化为上面的简单示例,因为我似乎对光纤和eventmachine应该如何一起使用有一个基本的误解,这使我无法有效地使用更复杂的框架。
最佳答案
你可能想要这样的东西:
require "eventmachine"
require "fiber"
def value
current_fiber = Fiber.current
EM.add_timer(2) do
puts "B"
current_fiber.resume("D") # Wakes the fiber
end
Fiber.yield # Suspends the Fiber, and returns "D" after #resume is called
end
EM.run do
Fiber.new {
puts "A"
val = value
puts "C"
puts val
EM.stop
}.resume
puts "(Async stuff happening)"
end
这将产生以下结果:
A
(Async stuff happening)
B
C
D
更具概念性的解释:
纤程有助于解开异步代码,因为它们将挂起并重新引导大块代码,这与手动线程非常相似。这就允许在事情发生的顺序上使用巧妙的技巧。一个小例子:
fiberA = Fiber.new {
puts "A"
Fiber.yield
puts "C"
}
fiberB = Fiber.new {
puts "B"
Fiber.yield
puts "D"
}
fiberA.resume # prints "A"
fiberB.resume # prints "B"
fiberA.resume # prints "C"
fiberB.resume # prints "D"
因此,当在光纤上调用
#resume
时,它将恢复执行,无论是从块的开始(对于新光纤),还是从以前的Fiber.yield
调用开始,然后执行,直到找到另一个Fiber.yield
或块结束。需要注意的是,将一系列动作放在光纤中是一种表示它们之间时间依赖关系的方法(
puts "C"
不能在puts "A"
之前运行),而“并行”光纤上的动作不能依赖(也不应该关心)其他光纤上的动作是否已执行:我们只有通过交换前两个resume
调用才能打印“bacd”。因此,这里是
rack-fiber_pool
的神奇之处:它将应用程序接收到的每个请求都放在光纤中(这意味着顺序独立),然后期望您在io操作上Fiber.yield
,以便服务器可以接受其他请求。然后,在eventmachine回调中,传入一个包含current_fiber.resume
的块,这样当对查询/请求/whatever的响应就绪时,您的纤程将重新启动。这已经是一个冗长的答案,但如果还不清楚,我可以提供一个eventmachine示例(我知道这是一个毛茸茸的概念,我挣扎了很多)。
更新:我已经创建了一个示例,它可能会帮助那些仍在概念上挣扎的人:https://gist.github.com/renato-zannon/4698724。我建议你跑去玩。