Модель актера: Почему эрланг особенный? Или, зачем вам нужен другой язык?

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

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

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

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

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

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

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

Ответ 1

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

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

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

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

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

edit: Просто нашел описание Ulf Wiger о том, что он видит erlang style concurrency as.

Ответ 2

Мне не нравится процитировать меня, но из Virding First Rule of Programming

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

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

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

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

Ответ 3

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

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

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

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

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

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

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

Ответ 5

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

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

Ответ 6

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

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

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

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

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

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

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