Основная фишка erlang — это все-таки его жуткая распределенность.
Модель программирования основана исключительно на посылке сообщений. То есть никакой расшаренной памяти, никаких,
мьютексов, евентов и прочей ерунды, обычно ассоциирующейся с параллельными (concurrent) приложениями.
Примитивных операций есть три:
- можно создать процесс с помощью функции spawn:
spawn(Function)
- можно ему послать сообщение с помощью выражения !
Pid ! Message
- и получить сообщение
receive
Pattern1 -> ...
Pattern2 -> ...
Pattern3 -> ...
after N ->
...
end
Конструкция receive очень похожа на выражение case, только для обработки таймаутов можно добавить кейс after N,
где N — время в мс, по истечении которого перестаем ждать ответа.
[Избитый] простейший пример, с процессами, пингующими друг друга:
-module(ping_test).
-compile(export_all).
ping() ->
Pid = spawn(fun pong/0),
Pid ! {ping, self()},
ping_loop(Pid).
ping_loop(Pid) ->
receive
{pong, Pid} ->
io:format("received pong~n"),
timer:sleep(random:uniform(1200)),
Pid ! {ping, self()},
ping_loop(Pid)
after 1000 ->
io:format("pong timed out~n")
end.
pong() ->
receive
{ping, Pid} ->
io:format("received ping~n"),
timer:sleep(random:uniform(1200)),
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
Рантайм обеспечивает, что сообщения доставляются в том же порядке, как и были отправлены, и никогда не выпадают из последовательности (то есть, если процесс N+1 сообщение не будет доставлено, если пропало N-ное).
Процессы можно легко и непринужденно запускать и на удаленных машинах, с помощью разновидности той же функции spawn. В большинстве случаев никакой разницы между локальными и удаленными процессами нет.
Чтобы превратить пример с ping в распределенное приложение, нужно просто заменить вызов
spawn(fun ...)
на
spawn(node, fun ...)
Где node это атом-имя узла на котором требуется запустить процесс. виртуальной машины. (А узел (node) — это просто запущенный экземпляр виртуальной машины Erlang).
Итак, немножко изменим функцию ping (модифицированный исходник тут):
ping(Node) ->
Pid = spawn(Node, fun pong/0),
Pid ! {ping, self()},
ping_loop(Pid).
Теперь запустим два интерпретатора в качестве узлов с именами boo и foo (оба должны иметь доступ к скомпилированному модулю, так что проще запустить их из одной директории):
$ 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
Создание процесса в Эрланге — очень дешевая операция (цифры — порядка сотен тысяч созданных и убитых процессов в секунду, см. бенчмарк здесь ).
Запущенные процессы также жрут очень мало памяти, а в режиме ожидания сообщений — еще и не жрут CPU (например, 500000 таких спящих процессов у меня ест 0% CPU и 600 Мб памяти — около 1.4 кб на процесс).
Механизмы обработки ошибок в Erlang, конечно, тоже распределенные.
В случае ошибки процесс умирает, и система рассылает всем процессам, связанным с ним специальное сообщение exit. Если тот процесс эти сообщения не отлавливает (для чего у процесса должен быть установлен специальный флаг trap_exit), то система убивает и его, и так далее по цепочке.
Связываются процессы с помощью функции link/2. Для удобства есть еще spawn_link/2, и если ее использовать в нашем примере вместо spawn, а также выходить из процессов не нормально, а с помощью erlang:error/1 (см. модифицированный исходник тут), вывод будет немного другим:
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
Не рекомендуется использовать другие механизмы, такие как коды ошибок — идеология "let it crash". Скажем, если функция принимает на вход строку — это проблема вызывающего не передавать туда атомы, числа и прочую хрень.
Хотя эти механизмы в Erlang не кажутся на первый взгляд чем-то сверхпродвинутым, именно они помогают писать сложные отказоустойчивые серверные приложения, уделяя при этом минимум времени на написание кода, обрабатывающего ошибки.
Читать дальше..