这里我的代码看起来
class Indefinite include Celluloid def run! loop do [1].each do |i| async.on_background end end end def on_background puts "Running in background" end end Indefinite.new.run!
但是,如果我睡觉,代码似乎工作.
class Indefinite include Celluloid def run! loop do [1].each do |i| async.on_background end sleep 0.5 end end def on_background puts "Running in background" end end Indefinite.new.run!
任何想法?为什么在上述两种情况下有这样的区别.
谢谢.
解决方法
你所有的程序正在做的是产生后台进程,但从不运行它们.你需要在循环中的睡眠才能让背景线程得到注意.
通过一个无条件的循环来产生无限的后台进程通常不是一个好主意,就像你在这里一样.应该有一个延迟或条件语句放在那里…否则你只有一个无限循环产生永远不会被调用的东西.
想像这样:如果你把循环放在你的循环中,而你没有看到在后台运行,你会看到循环一遍又一遍.
方法#1:使用每个或更多的块.
解决这个问题的最好方法是不要在循环中使用睡眠,而是使用一个或多个块,如下所示:
every(0.1) { on_background }
或者最重要的是,如果要确保进程在运行前完全运行,请再次运行:
def run_method @running ||= false unless @running @running = true on_background @running = false end after(0.1) { run_method } end
使用循环不是异步的好主意,除非有某种流量控制完成,或者使用@ server.accept等阻塞过程,否则只会将100%的cpu内核没有任何理由.
顺便说一句,你也可以使用now_and_every以及now_and_after,这样也可以立即运行该块,然后在你想要的时间后再运行一次.
使用每一个都显示在这个要点中:
> https://gist.github.com/digitalextremist/686f42e58a58b743142b
理想情况,在我看来:
这是一个粗略但立即可用的示例:
> https://gist.github.com/digitalextremist/12fc824c6a4dbd94a9df
require 'celluloid/current' class Indefinite include Celluloid INTERVAL = 0.5 ONE_AT_A_TIME = true def self.run! puts "000a Instantiating." indefinite = new indefinite.run puts "000b Running forever:" sleep end def initialize puts "001a Initializing." @mutex = Mutex.new if ONE_AT_A_TIME @running = false puts "001b Interval: #{INTERVAL}" end def run puts "002a Running." unless ONE_AT_A_TIME && @running if ONE_AT_A_TIME @mutex.synchronize { puts "002b Inside lock." @running = true on_background @running = false } else puts "002b Without lock." on_background end end puts "002c Setting new timer." after(INTERVAL) { run } end def on_background if ONE_AT_A_TIME puts "003 Running background processor in foreground." else puts "003 Running in background" end end end Indefinite.run! puts "004 End of application."
这将是其输出,如果ONE_AT_A_TIME为真:
000a Instantiating. 001a Initializing. 001b Interval: 0.5 002a Running. 002b Inside lock. 003 Running background processor in foreground. 002c Setting new timer. 000b Running forever: 002a Running. 002b Inside lock. 003 Running background processor in foreground. 002c Setting new timer. 002a Running. 002b Inside lock. 003 Running background processor in foreground. 002c Setting new timer. 002a Running. 002b Inside lock. 003 Running background processor in foreground. 002c Setting new timer. 002a Running. 002b Inside lock. 003 Running background processor in foreground. 002c Setting new timer. 002a Running. 002b Inside lock. 003 Running background processor in foreground. 002c Setting new timer. 002a Running. 002b Inside lock. 003 Running background processor in foreground. 002c Setting new timer.
如果ONE_AT_A_TIME为假,这将是其输出:
000a Instantiating. 001a Initializing. 001b Interval: 0.5 002a Running. 002b Without lock. 003 Running in background 002c Setting new timer. 000b Running forever: 002a Running. 002b Without lock. 003 Running in background 002c Setting new timer. 002a Running. 002b Without lock. 003 Running in background 002c Setting new timer. 002a Running. 002b Without lock. 003 Running in background 002c Setting new timer. 002a Running. 002b Without lock. 003 Running in background 002c Setting new timer. 002a Running. 002b Without lock. 003 Running in background 002c Setting new timer. 002a Running. 002b Without lock. 003 Running in background 002c Setting new timer.
您需要比“线程”更多地“发送”,以正确发出任务并保留范围和状态,而不是在线程/ actors之间发出命令,这是每个和每个块提供的内容.除此之外,无论哪种方式,即使您没有Global Interpreter Lock来处理这种做法也是一个很好的做法,因为在您的示例中,您似乎并不喜欢处理阻止进程.如果你有一个阻塞过程,那么一切都有一个无限循环.但是,由于您只是在处理之前最终产生无限数量的后台任务,所以您需要像开始使用您的问题一样使用睡眠,或者使用完全不同的策略,然后使用每一个Celloloid本身如何鼓励您在处理任何类型的插座上的数据时进行操作.
这刚刚出现在Google集团.下面的示例代码实际上将允许执行其他任务,即使它是一个无限循环.
> https://groups.google.com/forum/#!topic/celluloid-ruby/xmkdrMQBGbY
这种方法不太可取,因为它可能会有更多的开销,产生一系列的纤维.
def work # ... async.work end
问题2:线程与光纤行为.
第二个问题是为什么下面的工作:循环{Thread.new {puts“Hello”}}
这产生了无数个进程线程,由线程直接管理.即使您正在使用RVM中的全局解释器锁定,这仅意味着不会使用绿色线程,这些线程由操作系统本身提供,而是由进程本身处理.该进程的cpu调度程序会毫不犹豫地运行每个线程本身.在这个例子的情况下,线程运行非常快,然后死亡.
与异步任务相比,使用了光纤.所以发生的是这样的,在默认情况下:
>进程启动.
>演员实例化.
方法调用调用循环.
循环调用异步方法.
> async方法将任务添加到邮箱.
>邮箱不被调用,循环继续.
>另一个异步任务被添加到邮箱.
>这继续无限.
以上是因为循环方法本身是一个光纤呼叫,它不会被暂停(除非调用了一个睡眠!),因此添加到邮箱的附加任务决不是调用一个新的光纤.光纤的行为与线程不同.这是讨论差异的很好的参考资料:
> https://blog.engineyard.com/2010/concurrency-real-and-imagined-in-mri-threads
问题3:赛璐id与赛璐id :: ZMQ行为.
第三个问题是为什么Celluloid的行为与Celluloid :: ZMQ不同?
这是因为Celluloid :: ZMQ使用基于反应器的事件邮箱,而Celluloid使用基于条件变量的邮箱.
详细了解流水线和执行模式:
> https://github.com/celluloid/celluloid/wiki/Pipelining-and-execution-modes
这是两个例子之间的区别.如果您对这些邮箱的行为有其他问题,请随时在Google Group上发布…您所面临的主要动态是GIL与Fiber vs. Thread vs. Reactor行为相互作用的独特性质.
您可以在这里阅读有关反应堆模式的更多信息:
> http://en.wikipedia.org/wiki/Reactor_pattern
> Explanation of the “Reactor pattern”
> What is the difference between event driven model and reactor pattern?
并查看Celluloid :: ZMQ使用的特定反应器:
> https://github.com/celluloid/celluloid-zmq/blob/master/lib/celluloid/zmq/reactor.rb
那么在事件邮箱场景中发生的情况是,当打入睡眠时,这是一个阻塞调用,这将导致反应器移动到邮箱中的下一个任务.
而且,这是您的情况独特的,Celluloid :: ZMQ使用的特定反应堆正在使用一个永恒的C库,特别是0MQ库.该反应器在您的应用程序外部,其行为与Celluloid :: IO或Celluloid本身不同,这也是为什么行为发生与预期不同的原因.
多核支持选择
如果维护状态和范围对您不重要,如果使用不限于一个操作系统线程的jRuby或Rubinius,而使用具有全球解释器锁定的MRI,则可以实例化多个actor并在actor之间发出异步调用同时.
但我的谦虚的观点是,使用非常高频率的定时器,比如我的例子中的0.001或0.1,这对于所有意图和目的来说似乎是瞬间的,而且还允许演员线程有足够的时间来切换光纤并在邮箱中运行其他任务.