Понимание целлулоида Concurrency

Ниже приведены мои целлулоидные коды.

  • client1.rb Один из двух клиентов. (Я назвал его клиентом 1)

  • client2.rb 2-й из 2-х клиентов. (называемый клиентом 2)

Примечание:

единственная разница между двумя выше указанными клиентами - это текст, который передается серверу. iee ('client-1' и 'client-2' соответственно)

При тестировании этих 2 клиентов (путем их параллельной работы) против следующих 2 серверов (по одному на время). Я нашел очень странные результаты.

  1. server1.rb (базовый пример, взятый из README.md целлулоида-zmq)

    Использование этого в качестве примера сервера для 2 вышеуказанных клиентов привело к параллельным выполнению задач.

OUTPUT

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 находился во сне. (знак parallelism)

  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

Примечание:

клиент-2 попросил подождать 60 секунд, так как клиент-1 спал (60 секунд сна)

Я выполнил вышеупомянутый тест несколько раз, все это привело к такому же поведению.

Может ли кто-нибудь объяснить мне результаты вышеуказанных тестов.

Вопрос: Почему целлулоид должен ждать 60 секунд, прежде чем он сможет обработать другой запрос, например, как заметил в случае server2.rb.?

версия Ruby

ruby -v

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

Ответ 1

Используя ваши сущности, я подтвердил, что эта проблема может быть воспроизведена в MRI 2.2.1, а также jRuby 1.7.21 и Rubinius 2.5.8... Разница между server1.rb и server2.rb заключается в использовании DisplayMessage и message метод класса в последнем.


Использование sleep в DisplayMessage вне области Celluloid.

Когда sleep используется в server1.rb, он использует Celluloid.sleep в действительности, но при использовании в server2.rb используется Kernel.sleep... который блокирует почтовый ящик для Server до 60 секунд Прошло. Это предотвращает будущие вызовы метода для того, чтобы этот субъект обрабатывался до тех пор, пока почтовый ящик не обработает сообщения (вызовы методов для актера) еще раз.

Существует три способа решения этой проблемы:

  • Используйте блок defer {} или future {}.

  • Явно вызывать Celluloid.sleep, а не sleep (если явно не вызываться как Celluloid.sleep, использование sleep будет end the call Kernel.sleep, так как DisplayMessage не include Celluloid как Server делает)

  • Принесите содержимое 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... поэтому defer и future - моя лучшая рекомендация. Но публиковать что-то здесь, что вышло в моих комментариях:

Использование defer или future вызывает задачу, которая заставит актера привязаться к другому потоку. Если вы используете future, вы можете получить возвращаемое значение после выполнения задачи, если вы используете defer, вы можете запускать и забывать.

Но еще лучше, создайте другого актера для задач, которые, как правило, связаны друг с другом, и даже пускают этот другой актер... если defer или future не работают для вас.

Я был бы более чем счастлив ответить на последующие вопросы, поднятые этим вопросом; у нас есть очень активный активный список рассылки и канал IRC. Ваши щедрые щедрости заслуживают похвалы, но многие из нас помогут вам просто помочь.

Ответ 2

<суб > Удалось воспроизвести и устранить проблему. Удаление моего предыдущего ответа. По-видимому, проблема кроется в sleep. Подтверждено добавлением журналов "actor/kernel sleeping" в локальную копию Celluloids.rb sleep(). Суб >


В server1.rb,

вызов sleep находится внутри server - класса, который включает в себя Celluloid.

Таким образом, реализация целлулоида 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 sleep()

Это приостанавливает только текущий "актер" в Целлулоиде то есть только текущая "целлулоидная нить", обрабатывающая клиент1, спит.


В server2.rb,

вызов sleep находится в пределах другого класса DisplayMessage, который не включает Celluloid.

Таким образом, это собственный нативный sleep.

class DisplayMessage
    def self.message(message)

           ...

           sleep 60
    end
end

Обратите внимание на ОТСУТСТВИЕ любого журнала actor sleeping из server2.rb.

Это приостанавливает текущую рубиновую задачу, то есть рубиновый сервер засыпает (а не только одного актера Целлулоида).


Исправление?

В 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