Подтвердить что ты не робот

Как "стать" реализовано на языках, поддерживающих модель актера?

Модель актера хорошо описана Гулом Агой в его техническом отчете "Актеры: модель параллельных вычислений в распределенных системах".

На стр. 49 он объясняет команду "стать":

become <expression>

После вызова "стать X" актер отправит все свои сообщения в другой почтовый ящик (X).

Я не уверен, однако, как это реализовано (оно вообще реализовано) на таких языках, как Erlang и Scala. Это somethign мне нужно закодировать вручную? Как насчет эффективности? Agha показывает реализацию стека, используя передачу сообщений. Каждый раз, когда выполняется поп или нажатие, к какой-то актеру добавляется еще одна ссылка для пересылки. После нескольких сотен тысяч операций я ожидал, что такая реализация будет тратить слишком много времени на пересылку сообщений и не выполнять фактическую работу, если только некоторые под капотом выполнялась хорошая оптимизация.

Итак, мой вопрос: как пересылка (или "стать" ) реализована на типичных языках-актерах, таких как Erlang, Scala (и библиотеки для других языков)?

4b9b3361

Ответ 1

Посмотрите на стр. 12 (страница 26 из копии PDF, которую я имею) из статьи Аги "Актеры: модель параллельных вычислений в распределенной системе". "стать" - это его актерский язык, как вы указываете # 2, новое поведение для актера. Перенаправление сообщений другому актеру - одно из многих возможных новых способов поведения.

Я думаю, что с Scala актерами вы находитесь в основном той же лодке, что и с Erlang, если вы хотите, чтобы поведение пересылки. Под капотом Scala "реагировать" и "reactWithin" работают так же, как становятся, потому что частичная функция, определяемая блоком реакции, - это новое поведение актера, но я не уверен, что сходство даже преднамеренно.

Большинство (все?) "актерские" реализации довольно существенно отклоняются от модели актера Хьюитта и актерского языка Agha. IIRC часть языка от определения поведения актеров в Agha langauge даже не является полной Тьюрингом. Язык в целом становится только дополнением Тьюринга, когда вы рассматриваете конфигурацию актеров. Я бы сказал, что связь между моделью актера и текущими структурами актеров похожа на отношение объектной ориентации в SmallTalk к объектной ориентации на С++. Там есть некоторые передачи концепции и подобные термины, но в деталях они очень разные.

Ответ 2

Он не реализован непосредственно в Erlang, но вы можете написать тривиальную функцию become, которая получает сообщение, передает его другому процессу и затем вызывает себя:

become(Pid) ->
    receive
        Msg -> Pid ! Msg
    end,
    become(Pid).

(Промышленно-силовая версия этого, возможно, должна иметь дело с сигналами и другими разногласиями, но в этом суть.)

Вызов become(Pid) фактически превратит вызывающий процесс в процесс Pid с точки зрения внешнего мира.

Это не касается проблем, которые вы выделяете, с повторными вызовами become, вызывающими рост цепей пересылки. Однако это обычно не происходит в Erlang, и я не уверен, как процессы Erlang сопоставляются с моделью Actor.

Ответ 3

Актер - контравариантный кофенектор, поэтому "стать" - это просто компас.

Другими словами, актер на сообщения типа T в основном является функцией типа (T = > Unit). И это просто функциональная композиция (возможно, с функцией идентичности).

Он реализован в Scalaz:

val a1 = actor(a => println(a))
val a2 = a1.comap(f)

Актер a2 применяет f к своим сообщениям и затем отправляет результат в a1.

Ответ 4

Идём здесь Эрланг.

На базовом уровне доступны два варианта. Если вы хотите использовать become для изменения поведения данного процесса (см. Пункт 2 списка в разделе 2.1.3), то это просто вопрос вызова следующего цикла с другой рекурсивной функцией:

loop(State) ->
     receive
          {normal, Msg} -> loop(State);
          {change, NewLoop} -> NewLoop(State)
     end.

Предполагая, что NewLoop является функцией более высокого порядка, всякий раз, когда вы отправляете сообщение {change, NewLoop} в процесс, первоначально запускающий функцию loop/1, в качестве определения будет использоваться NewLoop.

Второй вариант - это тот, где вы хотите, чтобы процесс работал как прокси (и изменял поведение). Это похоже на то, что предложил Марсело Кантос. Просто обработайте цикл цикла и пересылайте сообщения на новый (украсть его код):

become(Pid) ->
    receive
        Msg -> Pid ! Msg
    end,
    become(Pid).

Теоретически это делает то, о чем просила газета. На практике, однако, существуют риски при использовании второго варианта в реальной системе Erlang. При обмене данными между двумя процессами часто возникает идея "штамповать" сообщение с идентификатором процесса отправителя и что ответ будет помечен идентификатором процесса приемника. Можно было бы обменяться следующими сообщениями (это не код, а только ручная запись):
A = <0.42.0> <-- process identifier
B = <0.54.0>,
A: {A, "hello"},
B: {B, "hi"},
A: {A, "how are you?"}.
B: {B, "Fine!"}.

Поэтому, когда A ожидает сообщение от B, оно будет соответствовать только для них, используя шаблон, например {B, Message}. В случае переадресованного сообщения эта схема адресации становится недействительной и просто ломается.

Альтернативой могло бы быть использование ссылок (make_ref()) в качестве схемы адресации для сопоставления сообщений поверх возвращаемых Pids. Это отделит использование Pid как адреса и идентификатора, используя два разных объекта.

Возникает еще одна проблема, даже если адресация безопасна: зависимости процесса. Что происходит для названных процессов, сбоев процессов, мониторов и т.д.? У клиентских процессов могут быть мониторы, ссылки и еще что-то настроенные, чтобы убедиться, что ничего не получится, не будучи уведомленным. Модифицируя процесс маршрутизации, чтобы улавливать сигналы выхода и пересылать их, должно быть возможно сделать вещи более безопасными:

loop(State) ->
     receive
          {normal, Msg} -> loop(State);
          {become, Pid} ->
              process_flag(trap_exit, true), % catch exit signals as messages
              link(Pid),                     % make it so if one process crashes, the other receives the signal
              become(Pid)
     end.

become(Pid) ->
    receive
        {'EXIT', Pid, killed} -> exit(self(), kill);  %% uncatchable termination
        {'EXIT', Pid, normal} -> ok;                  %% die normally too
        {'EXIT', Pid, Reason} -> exit(Reason);        %% die for the same reason as Pid
        Msg -> Pid ! Msg                              %% forward the message
    end,
    become(Pid).

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

Несмотря на то, что это возможно и концептуально достаточно просто, чтобы представлять и делать такие вещи, как become, стандартная библиотека Erlang просто не рассматривалась с учетом второго варианта использования. Для приложений реального мира я могу только рекомендовать первый метод, который широко используется каждым приложением Erlang, которое существует прямо сейчас. Второй вариант является необычным и может вызвать проблемы.


* * При вызове функции become/1 в последнем примере должно быть ?MODULE:become(Pid), чтобы избежать потенциальных сбоев, связанных с загрузкой горячего кода в будущем. *Суб >

Ответ 5

Акка Актеры имеют концепцию "HotSwap", где вы можете отправить новую PartialFunction актеру, который заменяет существующий обработчик сообщений. Предыдущее запоминается и может быть восстановлено. Найдите "hotswap" на http://doc.akkasource.org/actors для деталей.