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

Однако я читал о Scala Actor framework в Программирование на 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 здесь не затрагиваются некоторые ключевые вопросы, что очень досадно, потому что в остальном это действительно отличная книга.

110
jqno 9 Авг 2009 в 20:14

5 ответов

Лучший ответ

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

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

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

78
James Sheppard 18 Июн 2013 в 14:25

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

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

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

21
oxbow_lakes 11 Авг 2009 в 06:25

Эти утверждения о «отбрасывании стека» также сбили меня с толку на некоторое время, и я думаю, что понял это сейчас, и теперь это мое понимание. В случае «приема» в сообщении происходит блокировка выделенного потока (с помощью 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) 
  }

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

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

20
Ashwin 7 Июн 2010 в 15:05

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

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

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

8
Andriy Plokhotnyuk 30 Ноя 2011 в 13:33

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

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

0
user3407472 11 Мар 2014 в 18:53