Модель акторов: в чем особенность Erlang / OTP? Вы могли бы использовать другой язык?

Я искал изучение Erlang / OTP и в результате читал (хорошо, бегло бегло) о модели акторов.

Насколько я понимаю, модель актора - это просто набор функций (выполняемых в облегченных потоках, называемых «процессами» в Erlang / OTP), которые взаимодействуют друг с другом только посредством передачи сообщений.

Это кажется довольно тривиальным для реализации на C ++ или на любом другом языке:

class BaseActor {
    std::queue<BaseMessage*> messages;
    CriticalSection messagecs;
    BaseMessage* Pop();
public:
    void Push(BaseMessage* message)
    {
        auto scopedlock = messagecs.AquireScopedLock();
        messagecs.push(message);
    }
    virtual void ActorFn() = 0;
    virtual ~BaseActor() {} = 0;
}

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

Теперь, как я понимаю, я упускаю, или, скорее, замалчиваю здесь одну важную проблему, а именно: отсутствие уступки означает, что один Актер может несправедливо потреблять слишком много времени. Но являются ли кроссплатформенные сопрограммы основным фактором, затрудняющим это в C ++? (Например, в Windows есть волокна.)

Что еще мне не хватает, или модель действительно настолько очевидна?


person Jonathan Winks    schedule 12.11.2011    source источник
comment
Цель языка программирования - помочь в выражении идеи или спецификации. Модель акторов является неявной в Erlang, поэтому, хотя вы можете выражать свои идеи в модели на любом языке, в Erlang она будет намного лучше, потому что шаблон для вас сделан.   -  person GManNickG    schedule 13.11.2011
comment
@GMan, как только котельная плита будет готова (я бы подумал, это было бы разово), в чем преимущество?   -  person Seth Carnegie    schedule 13.11.2011
comment
@SethCarnegie: Это действительно суть моего вопроса.   -  person Jonathan Winks    schedule 13.11.2011
comment
процессы erlang могут находиться на одной и той же машине или на разных физических машинах (и фактический код, который вы пишете для этого, более или менее идентичен), поэтому ваш пример кажется большим упрощением. А как насчет кода горячей замены, может ли С ++ сделать это легко? Запущена ли память ваших актеров c ++ в песочницу?   -  person Kevin    schedule 13.11.2011
comment
кстати, в scala есть модель акторов (Akka), так что акторы определенно не ограничиваются erlang. Но я не думаю, что это так просто сделать на C ++, как вы думаете (по крайней мере, без больших ограничений).   -  person Kevin    schedule 13.11.2011
comment
@Kevin: Та же машина / другая машина: Не невозможно. Однако сложнее, чем фрагмент кода. Но опять же: единовременная стоимость внедрения. Код с горячей заменой: не связан с моделью актора и, следовательно, выходит за рамки этого вопроса. Песочница памяти: Нет. Это также, вероятно, невозможно (в C ++), и я не считаю это обязательным. C ++ - это не язык для няни.   -  person Jonathan Winks    schedule 13.11.2011
comment
Разве все не требует единовременной стоимости внедрения? Если серьезно, то я считаю, что отсутствие изолированной среды - серьезный недостаток, поскольку один субъект может вывести из строя всю систему.   -  person Kevin    schedule 13.11.2011
comment
@Kevin: Достаточно верно. Однако я пытался подчеркнуть, что после относительно небольших усилий по реализации вы продолжаете использовать тот же язык. Это означает, что вам не нужно изучать особенности другого языка, и вы не обременены его производительностью. Понимание языка и производительность имеют решающее значение для хорошо спроектированных распределенных систем. Зачем вам переключать языки, чтобы писать самые сложные части кода, если это стоит того, как в краткосрочной, так и в долгосрочной перспективе, хуже. У меня был вопрос: действительно ли модель актера настолько проста? Если нет, то что мне не хватает?   -  person Jonathan Winks    schedule 13.11.2011
comment
Я не знаю всех ответов, но вы можете начать с чтения о проекте Akka, который является актерами для java. У него есть некоторые ограничения по отношению к акторам erlang, так что это может указать вам, что легко, а что сложно.   -  person Kevin    schedule 13.11.2011
comment
Если вы можете реализовать безопасный, надежный, параллельный и обслуживаемый код на C ++ с такими же небольшими усилиями, как люди в erlang, тогда вперед. Однако в этом фрагменте не хватает тонны. Ядро erlang - надежность. Если процесс не может выполнить свою задачу, он терпит неудачу, и это сообщение об ошибке распространяется по системе, позволяя сложным графам зависимости реорганизоваться при различных типах сбоев (или ошибок). Вы можете это сделать, но вы должны спросить, почему этого никто не делает. Вот что ведет к новым языкам.   -  person Dustin    schedule 13.11.2011
comment
@ Дастин: Это именно то, о чем я прошу. Re: Что мне не хватает. Фрагмент был написан за минуту, и в него вложено столько же размышлений, так что очевидно, что он не завершен.   -  person Jonathan Winks    schedule 13.11.2011
comment
@Seth: это было бы разовое размышление, я бы подумал Да ... нет. Я не знаю ни одного человека, который с первого раза напишет что-нибудь идеальное. Вы не можете придумать никакого способа улучшить этот код? Или существующая реализация Erlang?   -  person GManNickG    schedule 13.11.2011
comment
@GMan Я не имел в виду, что ты напишешь что-то один раз, я имел в виду, что тебе не нужно писать это для каждой программы.   -  person Seth Carnegie    schedule 13.11.2011
comment
Я удивлен, что SO не закрыл гестапо. Но вы правы в том, что моделирование актеров можно выполнять на C ++ и даже на C. Очевидно, это требует гораздо больше усилий и может стать синтаксически запутанным.   -  person    schedule 06.06.2012


Ответы (6)


Код C ++ не имеет отношения к справедливости, изоляции, обнаружению ошибок или распределению, которые являются всем тем, что Erlang привносит как часть своей модели акторов.

  • Ни одному актеру не разрешается морить голодом любого другого актера (справедливость)
  • Если один актер выйдет из строя, это повлияет только на этого актера (изоляция)
  • Если один субъект выходит из строя, другие участники должны иметь возможность обнаруживать этот сбой и реагировать на него (обнаружение неисправности).
  • Актеры должны иметь возможность общаться по сети, как если бы они были на одной машине (распространение)

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

Вдобавок все библиотеки и инструменты, написанные на Erlang, могут предполагать, что мир работает именно так, и быть спроектированы соответствующим образом.

Это не невозможно сделать в C ++, но становится все труднее, если вы добавите тот факт, что Erlang работает почти со всеми основными конфигурациями аппаратного и ОС.

изменить: только что нашел описание Ульфа Вигера о том, как он видит параллелизм в стиле Erlang.

person Lukas    schedule 12.11.2011
comment
Я бы определенно включил изоляцию процесса и обработку ошибок в модель параллелизма erlang, иначе то, что пишет Ульф, очень хорошо. - person rvirding; 13.11.2011
comment
Все перечисленные вами свойства предоставляются процессам операционной системой. Программы на C ++ могут легко использовать их, как и любая другая программа. Я думаю, что ключ к Erlang заключается в том, что его субъекты намного дешевле, чем процессы ОС для предоставления этих свойств. В результате актеры могут использоваться более свободно. - person Karmastan; 13.11.2011
comment
@Karmastan Да, процессы Erlang очень дешевы, потому что / так параллелизм является основной абстракцией структурирования приложений. Мы предпочитаем называть их процессами, а не акторами, мы не слышали об акторах, когда создавали Erlang. :-) - person rvirding; 14.11.2011

Я не люблю цитировать себя, но из Первое правило программирования Вирдинга

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

Что касается Гринспана. Джо (Армстронг) придерживается аналогичного правила.

Проблема не в том, чтобы внедрить актеров, это не так уж и сложно. Проблема в том, чтобы все работало вместе: процессы, связь, сборка мусора, языковые примитивы, обработка ошибок и т. Д. Например, использование потоков ОС плохо масштабируется, поэтому вам нужно делать это самостоятельно. Это все равно что пытаться «продать» объектно-ориентированный язык, где у вас может быть только 1k объектов, и их тяжело создавать и использовать. С нашей точки зрения, параллелизм - это основная абстракция для структурирования приложений.

Увлекаюсь, поэтому остановлюсь на этом.

person rvirding    schedule 13.11.2011
comment
так что остановлюсь на этом: больше было бы интересно - person serv-inc; 26.10.2019

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

Чтобы добавить оттенка и акцента к другим отличным ответам, которые уже здесь, подумайте, что Erlang убирает (по сравнению с традиционными языками общего назначения, такими как C / C ++), чтобы добиться отказоустойчивости и времени безотказной работы.

Во-первых, снимает замки. В книге Джо Армстронга излагается этот мысленный эксперимент: предположим, что ваш процесс получает блокировку, а затем сразу дает сбой (сбой памяти вызывает сбой процесса или отключение питания части системы). В следующий раз, когда процесс ожидает той же блокировки, система только что зашла в тупик. Это может быть очевидная блокировка, как в вызове AquireScopedLock () в примере кода; или это может быть неявная блокировка, полученная от вашего имени менеджером памяти, например, при вызове malloc () или free ().

В любом случае сбой вашего процесса остановил работу всей системы. Фини. Конец истории. Ваша система мертва. Если вы не можете гарантировать, что каждая библиотека, которую вы используете в C / C ++, никогда не вызывает malloc и никогда не получает блокировку, ваша система не является отказоустойчивой. Системы Erlang могут и действительно уничтожают процессы по желанию, когда они находятся под большой нагрузкой, чтобы прогрессировать, поэтому при масштабировании ваши процессы Erlang должны быть уничтожаемыми (в любой отдельной точке выполнения), чтобы поддерживать пропускную способность.

Существует частичный обходной путь: использование повсюду аренды вместо блокировок, но у вас нет гарантии, что все библиотеки, которые вы используете, также сделают это. И логика и рассуждения о правильности быстро становятся непростыми. Более того, аренда восстанавливается медленно (по истечении тайм-аута), поэтому вся ваша система стала очень медленной перед лицом сбоя.

Во-вторых, Erlang устраняет статическую типизацию, что, в свою очередь, обеспечивает возможность горячей замены кода и одновременного запуска двух версий одного и того же кода. Это означает, что вы можете обновить свой код во время выполнения, не останавливая систему. Таким образом системы остаются работоспособными в течение девяти девяток или 32 мсек простоя в год. Их просто модернизируют на месте. Для обновления ваши функции C ++ необходимо будет повторно связать вручную, а одновременное выполнение двух версий не поддерживается. Обновление кода требует простоя системы, и если у вас есть большой кластер, который не может запускать более одной версии кода одновременно, вам нужно сразу отключить весь кластер. Ой. А в телекоммуникационном мире это не терпимо.

Вдобавок Erlang забирает разделяемую память и разделяемую совместно используемую сборку мусора; каждый легкий процесс собирает мусор независимо. Это простое расширение первого пункта, но подчеркивает, что для истинной отказоустойчивости вам нужны процессы, которые не заблокированы с точки зрения зависимостей. Это означает, что ваши паузы GC по сравнению с java допустимы (маленькие, вместо получасовой паузы для завершения GC на 8 ГБ) для больших систем.

person jaten    schedule 21.11.2012
comment
Во-первых, вы можете использовать lock_guard, который снимет вашу блокировку в случае сбоя программы. Во-вторых, вы можете реализовать систему горячей замены на C ++, но это головная боль ... Проблема с параллелизмом в том, что примитивы синхронизации, даже атомики, вводят ограничения и барьеры памяти и замедляют работу. Чем больше у вас потоков, тем больше вы замедлитесь. Erlang, как clojure или haskell, не использует мьютексы или атомики, что заставляет разработчика ставить проблему по-другому. Это очень эффективный способ решения проблем параллелизма. - person Asier Gutierrez; 28.06.2015
comment
Звучит правильно, но это только сравнение C ++, а C ++ всегда легко обвинить. Разве это невозможно, например, в Java (или Clojure)? Блокировки в Java безопасны, и есть способы компилировать / загружать код во время выполнения (в Clojure это тоже очень просто). - person Display Name; 29.06.2015

Существуют актуальные библиотеки акторов для C ++:

И список некоторых библиотек для других языков.

person Alexey Romanov    schedule 13.11.2011
comment
libcppa недавно была переименована в C ++ Actor Framework (CAF). Новый URL-адрес: github.com/actor-framework/actor-framework. - person mavam; 06.05.2015

Речь идет не столько о модели акторов, сколько о том, насколько сложно правильно написать что-то, аналогичное OTP на C ++. Кроме того, разные операционные системы предоставляют радикально разную отладку и системные инструменты, а виртуальная машина Erlang и несколько языковых конструкций поддерживают единый способ определения того, чем занимаются все эти процессы, что было бы очень сложно сделать единообразным способом (или, возможно, вообще) на нескольких платформах. (Важно помнить, что Erlang / OTP возник раньше, чем нынешний ажиотаж вокруг термина «модель акторов», поэтому в некоторых случаях такого рода дискуссии сравнивают яблоки и птеродактили; великие идеи склонны к самостоятельному изобретению.)

Все это означает, что, хотя вы, безусловно, можете написать набор программ «модель акторов» на другом языке (я знаю, я делал это долгое время на Python, C и Guile, не осознавая этого, прежде чем я столкнулся с Erlang, включая форму мониторов и ссылок, и еще до того, как я услышал термин «модель акторов»), понять, как на самом деле порождаются процессы вашего кода и что происходит среди них, чрезвычайно сложно. Erlang применяет правила, по которым ОС просто не может без капитального ремонта ядра - капитального ремонта ядра, который, вероятно, в целом не принесет пользы. Эти правила проявляются как как общие ограничения для программиста (которые всегда можно обойти, если вам действительно нужно), так и как базовые обещания, которые система гарантирует программисту (которые могут быть намеренно нарушены, если вам это действительно нужно).

Например, он требует, чтобы два процесса не могли совместно использовать состояние, чтобы защитить вас от побочных эффектов. Это не означает, что каждая функция должна быть "чистой" в том смысле, что все является ссылочно прозрачным (очевидно, нет, хотя сделать столько вашей программы ссылочно прозрачным, насколько это возможно, это четкая цель дизайна большинства Erlang projects), а скорее то, что два процесса не создают постоянно состояния гонки, связанные с общим состоянием или конфликтом. (Кстати, это больше означает, что «побочные эффекты» означают в контексте Erlang; знание этого может помочь вам расшифровать некоторые из дискуссий, в которых возникает вопрос, является ли Erlang «действительно функциональным или нет» по сравнению с Haskell или игрушечными «чистыми» языками. .)

С другой стороны, среда выполнения Erlang гарантирует доставку сообщений. Этого очень не хватает в среде, где вы должны общаться исключительно через неуправляемые порты, каналы, разделяемую память и общие файлы, которыми ядро ​​ОС управляет только (и управление этими ресурсами ядром ОС обязательно крайне минимально по сравнению с тем, что используется в Erlang). время выполнения предоставляет). Это не означает, что Erlang гарантирует RPC (в любом случае передача сообщений - это не RPC и не вызов метода!), Он не обещает, что ваше сообщение адресовано правильно, и это не так. обещайте, что процесс, которому вы пытаетесь отправить сообщение, существует или жив. Он просто гарантирует доставку, если вещь, которую вы отправляете, действительна в этот момент.

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

Так что на самом деле дело не в необходимости языка, как Erlang, а в уже существующей среде выполнения и OTP, выраженных довольно чисто, и чрезвычайно сложно реализовать что-либо близкое к нему на другом языке. OTP - это просто акт, которому сложно следовать. Точно так же нам не совсем нужен C ++, мы могли бы просто придерживаться сырого двоичного ввода, Brainfuck и считать Assembler нашим языком высокого уровня. Также нам не нужны поезда или корабли, так как все мы умеем ходить и плавать.

При этом байт-код виртуальной машины хорошо документирован, и появилось несколько альтернативных языков, которые компилируются с ней или работают со средой выполнения Erlang. Если мы разделим вопрос на языковую / синтаксическую часть («Должен ли я понимать лунные руны для параллелизма?») И платформенную часть («Является ли OTP наиболее зрелым способом обеспечения параллелизма, и поможет ли он мне решить самые сложные вопросы? , наиболее распространенные ошибки, которые можно найти в параллельной распределенной среде? "), то ответ будет (" нет "," да ").

person zxq9    schedule 02.09.2014

Casablanca - еще один новичок в блоке модели акторов. Типичный асинхронный прием выглядит так:

PID replyTo;
NameQuery request;
accept_request().then([=](std::tuple<NameQuery,PID> request)
{
   if (std::get<0>(request) == FirstName)
       std::get<1>(request).send("Niklas");
   else
       std::get<1>(request).send("Gustafsson");
}

(Лично я считаю, что CAF лучше скрывает сопоставление с образцом за красивым интерфейс.)

person mavam    schedule 01.05.2012