Scala актеры: получать и реагировать

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

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

Означает ли это, что до тех пор, пока я не помню, чтобы позвонить react вместо receive, я могу запустить столько актеров, сколько мне нравится? Прежде чем открыть Scala, я играл с Erlang, а автор Programming Erlang может похвастаться тем, что он размножил более 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 замаскировало некоторые из ключевых проблем здесь, что является позором, потому что в противном случае это действительно отличная книга.

Ответ 1

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

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

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

Ответ 2

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

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

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

Ответ 3

Те заявления о "отбрасывании стека" смутили меня и на некоторое время, и я думаю, что я понял это сейчас, и теперь это мое понимание. В случае "получения" на сообщение выделен выделенный поток потока (с помощью функции 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 actor Kilim фактически выполняет техническое обслуживание стека, сохраняя стек, который разворачивается при реагировании на получение сообщения.

Ответ 5

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

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