Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Riak и Erlang/OTP

Глава 15 из книги "Архитектура приложений с открытым исходным кодом", том 1.

Оригинал: "Riak and Erlang/OTP", глава из книги "The Architecture of Open Source Applications"
Авторы: Francesco Cesarini, Andy Gross and Justin Sheehy
Перевод: Н.Ромоданов

Creative Commons: Перевод был сделан в соответствие с лицензией Creative Commons. С русским вариантом лицензии можно ознакомиться здесь.

15.1. Краткое введение в язык Erlang

Язык Erlang является функциональным языком параллельного программирования, который компилируется в байт-код и работает в виртуальной машине. Программы состоят из функций, которые вызывают друг друга и часто возвращают результаты в виде межпроцессорных сообщений, представляющих собой побочный эффект, а также выполняют операции ввода вывода и обращаются к базе данных. Значения переменным языка Erlang присваиваются только один раз, то есть, если им было присвоено значение, оно не может быть изменено. В языке широко используется сравнение с шаблонами так, как это показано ниже на примере расчета факториала:

-module(factorial).
-export([fac/1]).
fac(0) -> 1;
fac(N) when N>0 ->
   Prev = fac(N-1),
   N*Prev.

Здесь первое предложение (clause) выдает факториал нуля, второе — выдает факториал положительных чисел. Тело каждого оператора представляет собой последовательность выражений, и последнее выражение в теле является результатом этого предложения. Вызов функции с отрицательным числом приведет к ошибке времени выполнения, поскольку предложение сравнения для такого случая отсутствует. Отсутствие обработки этого случая является примером небезопасного программирования, практика которого поощряется в языке Erlang.

Внутри модуля обращение к функции осуществляется обычным образом; вне модуля — сначала добавляется имя модуля, например, factorial:fac(3). Можно определять функции с тем же самым именем, но различным числом аргументов, это называется их арностью. В директиве export в модуле factorial арность функция fac равна единице, что обозначено как fac/1.

В языке Erlang поддерживается работа с кортежами (также называемыми продукционными типами), а также со списками. Кортежи заключаются в фигурные скобки, например, {ok,37}. В кортежах мы получаем доступ к элементам по их позиции. Записи являются другим типом данных, они позволяют нам хранить фиксированное количество элементов, доступ к которым и работа с ним осуществляется по имени. Мы определяем запись при помощи директивы -record(state, {id, msg_list=[]}). Чтобы создать экземпляр, мы используем выражение Var = #state{id=1}, и мы проверяем его содержимое при помощи Var#state.id. Если число элементов переменное, мы используем списки, которые записываются в квадратных скобках, например, {[}23,34{]}. Нотация {[}X|Xs{]} соответствует непустому списком с головой X и хвостом Xs. Идентификаторы, начинающиеся с маленькой буквы, обозначают атомы, которые просто представляют самих себя; ok в кортеже {ok,37} является примером атома. Атомы, используемые таким образом, часто применяются для указания различных видов результата функции: точно также как результаты ok, могут быть результаты вида {error, "Error String"}.

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

Поскольку процессы существуют только в виртуальной машине, одна виртуальная машина может одновременно выполнять миллионы процессов — это та особенность Riak, которой широко пользуются. Например, каждый запрос к базе данных — чтение, запись и удаление - моделируется как отдельный процесс, это тот подход, который в большинстве случаев нельзя реализовать с помощью работы с потоками на уровне ОС.

Процессы идентифицируется при помощи идентификаторов процессов, которые называются PID, но их также можно зарегистрировать с использованием алиаса; этим следует пользоваться только для долгоживущих "статических" процессов. Регистрация процесса с использованием алиаса позволяет другим процессам посылать ему сообщений, не зная его идентификатора PID. Процессы создаются с помощью встроенной функции spawn(Module, Function, Arguments)(built-in function -BIF). Функции BIF интегрированы в виртуальную машину и используются для того, чтобы делать то, что в чистом языке Erlang сделать нельзя или что делается слишком медленно. Встроенная функция spawn/3 получает в качестве параметров модуль Module, функцию Function и список аргументов Arguments. Вызов возвращает идентификатор PID только что порожденного процесса и, в качестве побочного эффекта, создает новый процесс, в котором начинается выполнение функции в модуле с аргументами, упомянутыми ранее.

Сообщение Msg отправляется в процесс с идентификатором Pid при помощи конструкции Pid ! Msg. Идентификатор PID процесса можно узнать, вызвав встроенную функцию self, и затем его можно отослать в другие процессы для того, чтобы они использовали его для обмена сообщениями с исходным процессом. Предположим, что процесс ожидает получения сообщений вида {ok, N} и {error, Reason}. Чтобы их обработать, используется следующая инструкция приема сообщений:

receive
   {ok, N} ->
      N+1;
   {error, _} ->
      0
end

Результатом работы этой инструкции является число, определяемое в предложении, в котором происходит сравнения с образцом. Если в сравнении с образцом не требуется определять значение переменной, то можно использовать универсальный символ подчеркивания так, как это показано выше.

Передача сообщений между процессами является асинхронной, и сообщения, принимаемые процессом, помещаются в почтовый ящик процесса в том порядке, в котором они поступают. Теперь предположим, что выражение receive, приведенное выше, выполняется: если первый элемент в почтовом ящике будет {ok, N} или {error, Reason}, то будет возвращен соответствующий результат. Если первое сообщение в почтовом ящике имеет другой вид, то оно сохраняется в почтовом ящике, и таким же самым образом обрабатывается второе сообщение. Если нет совпадающих сообщений, то выражение receive будет ждать до тех пор, пока не поступит совпадающее сообщение.

Процессы завершаются по двум причинам. Если для выполнения больше нет кода, то говорят, что процесс завершается по причине normal (нормальное завершение). Если в процессе во время выполнения возникают ошибки, то говорят, что он завершается по причине non-normal (ненормальное завершение). Завершение процесса не влияет на другие процессы, если они с ним не скомпонованы. Процессы можно скомпоновать друг с другом с помощью встроенной функции link(Pid) или при вызове spawn_link(Module, Function, Arguments). Если процесс завершается, то он посылает сигнал выхода EXIT в процессы, с которыми он скомпонован. Если происходит ненормальное завершение, то процесс завершается самостоятельно перенаправляя дальше сигнал EXIT. Если вызвать встроенную функцию process_flag(trap_exit, true), то вместе завершения процессы могут получить в своих почтовых ящиках сигналы EXIT, представляющие собой сообщения языка Erlang.

В Riak сигналы EXIT используются для мониторинга нормального состояния вспомогательных процессов обработки некритических операций, инициированных по запросам конечных автоматов. Если эти вспомогательные процессы завершаются ненормально, сигнал EXIT позволяет родительскому процессу либо игнорировать ошибку, либо перезапустить процесс.


Продолжение статьи: Остов процесса.