Как «стать» реализовано в языках, которые поддерживают модель актера? - PullRequest
12 голосов
/ 24 июля 2010

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

На странице 49 он объясняет команду «становиться»:

become <expression>

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

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

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

Ответы [ 5 ]

7 голосов
/ 24 июля 2010

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

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

(Industrial-Сила этого варианта может потребовать обработки сигналов и других шансов, но в этом суть.)

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

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

4 голосов
/ 25 июля 2010

Идем сюда на Эрланге.

На базовом уровне доступны две опции. Если вы хотите использовать 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, которое существует прямо сейчас. Второй необычный и может вызвать проблемы


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

4 голосов
/ 25 июля 2010

Актер является контравариантным ко-функтором, поэтому «становиться» - просто комап.

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

Реализовано в Scalaz:

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

Действующий субъект a2 применяет f к своим сообщениям, а затем отправляет результат a1.

3 голосов
/ 25 июля 2010

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

3 голосов
/ 24 июля 2010

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

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

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

...