Понимание целлулоида Concurrency
Ниже приведены мои целлулоидные коды.
Примечание:
единственная разница между двумя выше указанными клиентами - это текст, который передается серверу. iee ('client-1'
и 'client-2'
соответственно)
При тестировании этих 2 клиентов (путем их параллельной работы) против следующих 2 серверов (по одному на время). Я нашел очень странные результаты.
-
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)
-
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