了解赛璐珞并发

Understanding Celluloid Concurrency

以下是我的赛璐珞代码。

  1. client1.rb 2个客户之一。 (我将其命名为客户端1)

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

注:

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

针对以下 2 个服务器(一次一个)测试这 2 个客户端(运行 它们并排)。我发现非常 st运行ge 结果.

  1. server1.rb取自celluloid-zmqREADME.md的基本例子

    将其用作上述 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

注:

client2.rb 消息在 client1.rb 请求处于睡眠状态时被处理。(平行标记)

  1. server2.rb

    将此用作上述 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]

使用你的要点,我验证了这个问题可以在 MRI 2.2.1 以及 jRuby 1.7.21Rubinius 2.5.8 中重现...... server1.rb 和 [= 之间的区别16=]是在后者中使用了DisplayMessagemessageclass方法


DisplayMessage 中使用 sleep 超出 Celluloid 范围。

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

解决这个问题的方法有以下三种:

  • 使用 defer {}future {} 块。

  • 显式调用 Celluloid.sleep 而不是 sleep(如果未显式调用 Celluloid.sleep,使用 sleep 将最终调用 Kernel.sleep 因为 DisplayMessage 不像 Server 那样 include Celluloid )

  • DisplayMessage.message的内容放入handle_message,如server1.rb;或者至少进入 Server,它在 Celluloid 范围内,并且将使用正确的 sleep.


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是我最好的建议。但是 post 我的评论中出现了一些东西:

使用 deferfuture 推送一个任务,该任务会导致 actor 被绑定到另一个线程中。如果你使用 future,你可以在任务完成后获得 return 值,如果你使用 defer,你可以 fire & forget。

但更好的是,为容易被捆绑的任务创建另一个 actor,甚至将另一个 actor 汇集在一起​​......如果 deferfuture 不适合你。

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

设法重现并修复了问题。 删除我之前的回答。 显然,问题出在sleep。 通过将日志 "actor/kernel sleeping" 添加到 Celluloids.rb's sleep(). 的本地副本来确认


server1.rb

the call to sleep is within server - a class that includes Celluloid.

Thus Celluloid's implementation of sleep overrides the native sleep.

class Server
  include Celluloid::ZMQ

  ...

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

  def handle_message(message)

        ...

        sleep 60
  end
end

注意来自 server1.rb 的日志 actor sleeping。日志添加到Celluloids.rb's sleep()

这只会暂停 Celluloid 中的当前 "actor" 即只有当前 "Celluloid thread" 处理 client1 睡眠。


server2.rb

the call to sleep is within a different class DisplayMessage that does NOT include Celluloid.

Thus it is the native sleep itself.

class DisplayMessage
    def self.message(message)

           ...

           sleep 60
    end
end

请注意 server2.rb 中没有任何 actor sleeping 日志。

这会暂停当前的 ruby 任务,即 ruby 服务器休眠(不只是单个 Celluloid 演员)。


修复?

In server2.rb, the appropriate sleep must be explicitly specified.

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