Основная фишка erlang — это все-таки его жуткая распределенность.
Модель программирования основана исключительно на посылке сообщений. То есть никакой расшаренной памяти, никаких, Примитивных операций есть три: - можно ему послать сообщение с помощью выражения ! - и получить сообщение Конструкция receive очень похожа на выражение case, только для обработки таймаутов можно добавить кейс after N, [Избитый] простейший пример, с процессами, пингующими друг друга: Задержки там я вставил чтобы сымитировать как будто процессы действительно как-то серьезно обрабатывают сообщения, причем иногда не укладываются с этим в таймаут в одну секунду. Итак, запускаем: Рантайм обеспечивает, что сообщения доставляются в том же порядке, как и были отправлены, и никогда не выпадают из последовательности (то есть, если процесс N+1 сообщение не будет доставлено, если пропало N-ное). Процессы можно легко и непринужденно запускать и на удаленных машинах, с помощью разновидности той же функции spawn. В большинстве случаев никакой разницы между локальными и удаленными процессами нет.
Чтобы превратить пример с ping в распределенное приложение, нужно просто заменить вызов на Где node это атом-имя узла на котором требуется запустить процесс. виртуальной машины. (А узел (node) — это просто запущенный экземпляр виртуальной машины Erlang).
Итак, немножко изменим функцию ping (модифицированный исходник тут): Теперь запустим два интерпретатора в качестве узлов с именами boo и foo (оба должны иметь доступ к скомпилированному модулю, так что проще запустить их из одной директории): Создание процесса в Эрланге — очень дешевая операция (цифры — порядка сотен тысяч созданных и убитых процессов в секунду, см. бенчмарк здесь ). Запущенные процессы также жрут очень мало памяти, а в режиме ожидания сообщений — еще и не жрут CPU (например, 500000 таких спящих процессов у меня ест 0% CPU и 600 Мб памяти — около 1.4 кб на процесс). Механизмы обработки ошибок в Erlang, конечно, тоже распределенные. В случае ошибки процесс умирает, и система рассылает всем процессам, связанным с ним специальное сообщение exit. Если тот процесс эти сообщения не отлавливает (для чего у процесса должен быть установлен специальный флаг trap_exit), то система убивает и его, и так далее по цепочке. Связываются процессы с помощью функции link/2. Для удобства есть еще spawn_link/2, и если ее использовать в нашем примере вместо spawn, а также выходить из процессов не нормально, а с помощью erlang:error/1 (см. модифицированный исходник тут), вывод будет немного другим: Не рекомендуется использовать другие механизмы, такие как коды ошибок — идеология "let it crash". Скажем, если функция принимает на вход строку — это проблема вызывающего не передавать туда атомы, числа и прочую хрень. Хотя эти механизмы в Erlang не кажутся на первый взгляд чем-то сверхпродвинутым, именно они помогают писать сложные отказоустойчивые серверные приложения, уделяя при этом минимум времени на написание кода, обрабатывающего ошибки.
мьютексов, евентов и прочей ерунды, обычно ассоциирующейся с параллельными (concurrent) приложениями.
- можно создать процесс с помощью функции spawn: spawn(Function)
Pid ! Message
receive
Pattern1 -> ...
Pattern2 -> ...
Pattern3 -> ...
after N ->
...
end
где N — время в мс, по истечении которого перестаем ждать ответа.
-module(ping_test).
-compile(export_all).
ping() ->
% создаем процесс pong
Pid = spawn(fun pong/0),
% посылаем ему сообщение -- атом ping и свой Pid
Pid ! {ping, self()},
% ждем от него сообщений
ping_loop(Pid).
% обработка сообщений от процесса pong
ping_loop(Pid) ->
receive
{pong, Pid} ->
% получили сообщение pong и Pid отправителя
io:format("received pong~n"),
% ждем случайный интервал времени от 0 до 1200 мс.
timer:sleep(random:uniform(1200)),
% шлем ему опять сообщение ping
Pid ! {ping, self()},
ping_loop(Pid) % и опять ждем...
after 1000 ->
io:format("pong timed out~n")
end.
% входная точка процесса pong
pong() ->
receive
{ping, Pid} ->
% получили сообщение ping и Pid отправителя
io:format("received ping~n"),
% подождем..
timer:sleep(random:uniform(1200)),
% шлем обратно pong
Pid ! {pong, self()},
pong() % цикл
after 1000 ->
io:format("ping timed out~n")
end.
1> c(ping_test).
{ok,ping_test}
2> ping_test:ping().
received ping
received pong
received ping
received pong
received ping
received pong
received ping
connection to pong timed out
ok
3> connection to ping timed out
spawn(fun ...)
spawn(node, fun ...)
ping(Node) ->
Pid = spawn(Node, fun pong/0),
Pid ! {ping, self()},
ping_loop(Pid).
$ erl -name foo@127.0.0.1
$ erl -name boo@127.0.0.1
(boo@127.0.0.1)2> c(ping_distrib).
{ok,ping_distrib}
(boo@127.0.0.1)1> ping_distrib:ping('foo@127.0.0.1').
received ping
received pong
received ping
received pong
received ping
received pong
received ping
connection to pong timed out
ok
(boo@127.0.0.1)2> connection to ping timed out
4> ping_test:ping().
received ping
received pong
connection to ping timed out
=ERROR REPORT==== 2-Feb-2008::14:30:23 ===
Error in process <0.43.0> with exit
value: {ping_timeout,[{ping_test,pong,0}]}
** exception exit: pong
in function ping_test:pong/0
13 comments:
Как человек, развращённый Python-ом и Си, заявляю - ну и синтаксис! :)))
Хорошее дело затеял автор. Большое спасибо.
Сам сейчас потихоньку ерланг изучаю и с любопытством прочитал статьи. Жду продолжения :)
По мне так, механизм использующийся в ерланг это прошлый век. Доставка сообщений менее предсказуема и не линейна. Что в свою очередь рожает неограниченное количество ошибок, большее время разработки.
axet> По сравнению с чем?
2axet
она и не должна быть предсказуемой и линейной.
Смысл эрланга в том что система это набор общающихся между собой процессов. Если принять процесс за объект то получаем классическое ООП уровня смоллтолка, но с поддержкой распределённости прям из коробки.
Я правильно понимаю, что программа через некоторое время упадет со stack overflow? ping_loop вызывает сам себя.
Krasin> Нет, как и любой другой функциональный язык, Erlang поддерживает оптимизацию хвостовых вызовов -- т.е. этот рекурсивный вызов ping_loop будет преобразован компилятором в цикл.
О, спасибо. Я забыл про хвостовую рекурсию :)
очень понравилось (вместе с первой частью).
>Хотя эти механизмы в Erlang не кажутся на первый взгляд чем-то сверхпродвинутым, именно они помогают писать сложные отказоустойчивые серверные приложения
Если же добавить, что принципами дизайна заложено автоматическое восстановление упавших процессов(самовосстанавливающееся дерево), то совместно с возможностью замены кода на лету это может убедить каждого, на какие чудеса выносливости способен Эрланг :)
Спасибо за статью, но в реальном приложении почти всегда будет конкурентный ресурс, хранящий данные, доступ к которым для чтения и изменения потребуется нескольким процессам одновременно. Вариант с созданием процесса, являющегося интерфейсом к такому ресурсу, обрабатывающего сообщения других процессов в порядке очереди будет бутылочным горлышком такой системы. Да, сообщения добавляются в очередь и это добавляет устойчивости, но если процесс не успевает обрабатывать сообщения, то они накапливаются, постепенно сжирая память и в итоге он упадет. Как обрабатываются такие моменты в Erlang?
>>Спасибо за статью, но в реальном приложении почти всегда будет конкурентный ресурс, хранящий данные, доступ к которым для чтения и изменения потребуется нескольким процессам одновременно.
И тут мы вплотную подходим к вопросу использования БД... В Erlang есть встроенная БД mnesia для хранения небольших объемов данных, но можно использовать и более мощные РСУБД, если есть такая необходимость.
Erlang - функциональный язык, а то, что вы хотите, называется хранимым состоянием (что в ФП считается дурным тоном). Такими вещами программа заниматься не должна (в большинстве случаев), а должна вместо этого использовать спецсредства. СУБД - одно из таких средств. Прямая передача контекста через параметры функции - второй способ передачи хранимого состояния, но он не всегда применим.
>называется хранимым состоянием
он говорит не о хранимом состоянии.а о том, что будет если процесс не будет успевать разгребать свой ящик сообщений.
Post a Comment