Модель актера: почему эрланг особенный? Или зачем вам нужен другой язык? - PullRequest
74 голосов
/ 13 ноября 2011

Я изучал эрланг и, в результате, читал (ладно, скимминг) модель актера.

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

Это кажется довольно тривиальным для реализации в 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 есть волокна.)

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

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

Ответы [ 6 ]

83 голосов
/ 13 ноября 2011

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

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

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

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

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

edit: Только что нашел описание Ulf Wiger о том, что он видит в параллелизме в стиле эрланга.

29 голосов
/ 14 ноября 2011

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

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

По отношению к Гринспену. У Джо (Армстронг) есть похожее правило.

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

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

21 голосов
/ 21 ноября 2012

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

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

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

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

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

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

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

14 голосов
/ 13 ноября 2011

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

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

3 голосов
/ 02 сентября 2014

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

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

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

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

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

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

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

2 голосов
/ 01 мая 2012

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

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 лучше скрывает сопоставление с шаблоном за красивым интерфейсом.)

...