Scala актеры: получить против реагировать - PullRequest
110 голосов
/ 09 августа 2009

Позвольте мне сначала сказать, что у меня довольно большой опыт работы с Java, но только недавно он заинтересовался функциональными языками. Недавно я начал смотреть на Scala, который кажется очень хорошим языком.

Тем не менее, я читал об Scala Actor Framework в Программирование в Scala , и есть одна вещь, которую я не понимаю. В главе 30.4 говорится, что использование react вместо receive позволяет повторно использовать потоки, что хорошо для производительности, поскольку потоки в JVM дороги.

Значит ли это, что, если я не забуду позвонить react вместо receive, я могу запустить столько актеров, сколько захочу? Прежде чем открыть Scala, я играл с Эрлангом, и автор Программирование на Эрланге может похвастаться тем, что порождает более 200 000 процессов без единого пота. Я бы не хотел делать это с потоками Java. На какие ограничения я смотрю в Scala по сравнению с Erlang (и Java)?

Кроме того, как этот поток повторно использует работу в Scala? Давайте для простоты предположим, что у меня есть только один поток. Будут ли все актеры, с которыми я начинаю, работать последовательно в этой теме, или произойдет какое-то переключение задач? Например, если я запускаю двух актеров, которые пинг-понг сообщают друг другу, рискну ли я зайти в тупик, если они запущены в одном потоке?

Согласно Программирование в Scala , написание актеров для использования react сложнее, чем с receive. Это звучит правдоподобно, так как react не возвращается. Тем не менее, книга продолжает показывать, как вы можете поместить react в цикл, используя Actor.loop. В результате вы получите

loop {
    react {
        ...
    }
}

, который мне кажется довольно похожим на

while (true) {
    receive {
        ...
    }
}

, который используется ранее в книге. Тем не менее, в книге говорится, что «на практике программам потребуется по меньшей мере несколько receive». Так чего мне здесь не хватает? Что может receive сделать, чего не может react, кроме возврата? И почему меня это волнует?

Наконец, в центре всего того, что я не понимаю: в книге постоянно упоминается, как использование react позволяет отказаться от стека вызовов для повторного использования потока. Как это работает? Почему необходимо отказаться от стека вызовов? И почему стек вызовов может быть отброшен, когда функция завершается с помощью исключения (react), но не тогда, когда она завершается возвратом (receive)?

У меня сложилось впечатление, что Программирование в Scala затмило некоторые ключевые вопросы здесь, что является позором, потому что в остальном это действительно превосходная книга.

Ответы [ 5 ]

78 голосов
/ 09 августа 2009

Во-первых, каждый актер, ожидающий на receive, занимает поток. Если он ничего не получает, этот поток никогда ничего не сделает. Актер на react не занимает ни одного потока, пока не получит что-то. Как только он получает что-то, ему выделяется поток, и он инициализируется в нем.

Теперь важна часть инициализации. Ожидается, что принимающий поток вернет что-то, а реагирующий - нет. Таким образом, предыдущее состояние стека в конце последнего react может быть полностью отброшено. Отсутствие необходимости сохранять или восстанавливать состояние стека ускоряет запуск потока.

Существуют различные причины производительности, по которым вы можете захотеть одну или другую. Как вы знаете, слишком много потоков в Java не очень хорошая идея. С другой стороны, поскольку необходимо присоединить субъект к потоку, прежде чем он сможет react, он быстрее receive отправляет сообщение, чем react. Поэтому, если у вас есть актеры, которые получают много сообщений, но делают с ними очень мало, дополнительная задержка в react может сделать ее слишком медленной для ваших целей.

21 голосов
/ 11 августа 2009

Ответ «да» - если ваши актеры не блокируют что-либо в вашем коде, и вы используете react, то вы можете запустить «одновременную» программу в одном потоке (попробуйте установка системного свойства actors.maxPoolSize для выяснения).

Одна из наиболее очевидных причин, по которой необходимо отбросить стек вызовов , заключается в том, что в противном случае метод loop закончился бы StackOverflowError. На самом деле, фреймворк довольно умно завершает react, бросая SuspendActorException, который перехватывается циклическим кодом, который затем снова запускает react с помощью метода andThen.

Взгляните на метод mkBody в Actor, а затем на метод seq, чтобы увидеть, как цикл сам перепланируется - ужасно умные вещи!

20 голосов
/ 07 июня 2010

Эти заявления о «отбрасывании стека» также некоторое время смущали меня, и я думаю, что понял это сейчас, и это мое понимание сейчас. В случае «получения» существует специальная блокировка потока для сообщения (с использованием object.wait () на мониторе), и это означает, что полный стек потока доступен и готов продолжить с момента «ожидания» при получении сообщение. Например, если у вас был следующий код

  def a = 10;
  while (! done)  {
     receive {
        case msg =>  println("MESSAGE RECEIVED: " + msg)
     }
     println("after receive and printing a " + a)
  }

поток будет ожидать вызова при получении до получения сообщения, а затем продолжит работу и напечатает сообщение «после получения и печати 10» со значением «10», которое находится в кадре стека перед потоком заблокирован.

В случае реакции нет такого выделенного потока, все тело метода метода реакции захватывается как замыкание и выполняется некоторым произвольным потоком на соответствующем актере, получающем сообщение. Это означает, что будут выполняться только те операторы, которые могут быть зафиксированы как замыкание, и именно здесь возвращается тип возврата «Nothing». Рассмотрим следующий код

  def a = 10;
  while (! done)  {
     react {
        case msg =>  println("MESSAGE RECEIVED: " + msg)
     }
     println("after react and printing a " + a) 
  }

Если бы у реагирования был тип возврата void, это означало бы, что иметь законные операторы после вызова «реакции» (в примере оператор println, который печатает сообщение «после реакции и печати 10»), но в действительности это никогда не будет выполнено, так как только тело метода «реакции» будет захвачено и упорядочено для выполнения позже (по прибытии сообщения). Так как контракт реакции имеет тип возврата «Ничего», после реакции не может быть никаких операторов, и поэтому нет никаких причин для поддержки стека. В приведенном выше примере переменная «a» не должна поддерживаться, так как операторы после реагирующих вызовов вообще не выполняются. Обратите внимание, что все необходимые переменные в теле реакции уже записаны как замыкание, поэтому они могут выполняться очень хорошо.

Инфраструктура Java-актера Kilim фактически выполняет обслуживание стека, сохраняя стек, который развертывается при реакции на получение сообщения.

8 голосов
/ 15 июня 2010

Просто чтобы иметь это здесь:

Программирование на основе событий без инверсии управления

Эти документы связаны с Scala API для Actor и предоставляют теоретическую основу для реализации Actor. Это включает то, почему реакция может никогда не вернуться.

0 голосов
/ 11 марта 2014

Я не занимался какой-либо серьезной работой со scala / akka, однако я понимаю, что есть очень существенная разница в планировании актеров. Akka - это просто умный пул потоков, который нарезает время актерам ... Каждый раз, когда срез будет одним исполнением сообщения до завершения актером, в отличие от Эрланга, который может быть для каждой инструкции?!

Это заставляет меня думать, что реакция лучше, так как она подсказывает текущему потоку рассмотреть других участников для планирования, где при получении «может» задействовать текущий поток, чтобы продолжить выполнение других сообщений для того же самого субъекта.

...