了解赛璐珞并发

时间:2016-01-28 12:42:21

标签: ruby multithreading zeromq celluloid

以下是我的赛璐珞代码。

  1. client1.rb 2个客户中的一个。 (我把它命名为客户1)

  2. client2.rb 2个客户中的第2个。 (命名为客户2)

  3. 注意:

    上述2个客户端之间唯一的区别是传递给服务器的文本。,分别是'client-1''client-2'

    对这两个客户端(通过并排运行)对下面两台服务器(一次一台)进行测试。我发现非常奇怪的结果

    1. server1.rb取自celluloid-zmq的README.md的基本示例

      将此作为上述2个客户端的示例服务器,可以执行并行执行任务。

    2. 输出

      ruby server1.rb
      
      Received at 04:59:39 PM and message is client-1
      Going to sleep now
      Received at 04:59:52 PM and message is client-2
      

      注意:

      client1.rb 请求处于休眠状态时,处理 client2.rb 消息。(并行标记

      1. server2.rb

        将此作为上述2个客户的示例服务器导致并行执行任务。

      2. 输出

        ruby server2.rb
        
        Received at 04:55:52 PM and message is client-1
        Going to sleep now
        Received at 04:56:52 PM and message is client-2
        

        注意:

        client-2 被要求等待60秒,因为 client-1 正在睡觉(60秒睡眠)

        我多次运行上述测试都导致了相同的行为。

        任何人都可以从上述测试的结果中解释我。

        问题:为什么赛璐珞会在处理其他请求之前等待60秒,如在server2.rb情况下注意到的那样。?

        Ruby版本

        ruby -v

        ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin13.0]

2 个答案:

答案 0 :(得分:6)

使用您的要点,我确认此问题可以在MRI 2.2.1以及jRuby 1.7.21Rubinius 2.5.8中重现... server1.rb和{{1}之间的区别在后者中使用server2.rbDisplayMessage类方法。

message中使用sleep超出DisplayMessage范围。

Celluloid中使用sleep时,它实际上正在使用server1.rb,但在Celluloid.sleep中使用时,它正在使用server2.rb ...将Kernel.sleep的邮箱向上,直到60秒过去。这可以防止将来处理该actor的方法调用,直到邮箱再次处理消息(对actor的方法调用)。

有三种解决方法:

  • 使用defer {}Server阻止。

  • 明确调用Celluloid.sleep而不是future {}(如果没有明确调用sleep,则使用Celluloid.sleep将最终调用sleep,因为{{ 1}}不像Kernel.sleep那样DisplayMessage

  • include Celluloid的内容带入Server DisplayMessage.message;或至少handle_message范围内的server1.rb,并使用正确的sleep

Server方法:

Celluloid

defer {}方法:

def handle_message(message)
  defer {
    DisplayMessage.message(message)
  }
end

不是真正的范围问题;它是关于异步的。

重申一下,更深层次的问题不是Celluloid.sleep的范围......这就是为什么class DisplayMessage def self.message(message) #de ... Celluloid.sleep 60 end end sleep是我最好的建议。但是在我的评论中发布一些内容:

使用deferfuture推送一个任务,该任务会导致actor绑定到另一个线程。如果您使用defer,则可以在任务完成后获得返回值,如果您使用future则可以触发&忘记。

但更好的是,为那些倾向于被束缚的任务创建另一个角色,甚至将其他演员聚集在一起......如果futuredefer不适合你。< / p>

我非常乐意回答这个问题提出的后续问题;我们有一个非常active mailing list和IRC频道。你慷慨的赏金是值得称道的,但是我们很多人会纯粹帮助你。

答案 1 :(得分:3)

<子> 管理以重现并修复问题。 删除我以前的答案。 显然,问题在于sleep。 通过将日志"actor/kernel sleeping"添加到Celluloids.rb's sleep()的本地副本确认。

server1.rb

  

sleep调用在server范围内 - 包含赛璐珞的类。

     

因此,赛璐珞的sleep实施会覆盖原生sleep

class Server
  include Celluloid::ZMQ

  ...

  def run
    loop { async.handle_message @socket.read }
  end

  def handle_message(message)

        ...

        sleep 60
  end
end

请注意actor sleeping中的日志server1.rb。记录已添加到Celluloids.rb's sleep()

这仅暂停Celluloid中的当前“演员” 即只有处理client1的当前“Celluloid线程”才会休眠。

server2.rb

  

sleep 的调用属于不包含赛璐珞的不同类DisplayMessage

     

因此它本身就是sleep本身。

class DisplayMessage
    def self.message(message)

           ...

           sleep 60
    end
end

请注意来自actor sleeping的任何server2.rb日志的缺席。

这会挂起当前的ruby任务,即ruby服务器休眠(不只是一个Celluloid actor)。

修复?

  

server2.rb中,必须明确指定相应的sleep

class DisplayMessage
    def self.message(message)
        puts "Received at #{Time.now.strftime('%I:%M:%S %p')} and message is #{message}"
        ## Intentionally added sleep to test whether Celluloid block the main process for 60 seconds or not.
        if message == 'client-1'
           puts 'Going to sleep now'.red

           # "sleep 60" will invoke the native sleep.
           # Use Celluloid.sleep to support concurrent execution
           Celluloid.sleep 60
        end
    end
end