Учебники

Эликсир — Процессы

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

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

Функция появления

Самый простой способ создать новый процесс — использовать функцию spawn . Спаун принимает функцию, которая будет запущена в новом процессе. Например —

pid = spawn(fn -> 2 * 2 end)
Process.alive?(pid)

Когда вышеуказанная программа запущена, она дает следующий результат —

false

Возвращаемое значение функции spawn — PID. Это уникальный идентификатор процесса, поэтому, если вы запустите код над своим PID, он будет другим. Как вы можете видеть в этом примере, процесс мертв, когда мы проверяем, жив ли он. Это потому, что процесс завершится, как только завершит выполнение данной функции.

Как уже упоминалось, все коды Elixir выполняются внутри процессов. Если вы запустите функцию self, вы увидите PID для вашего текущего сеанса —

pid = self
 
Process.alive?(pid)

Когда вышеуказанная программа запущена, она дает следующий результат —

true

Передача сообщений

Мы можем отправлять сообщения процессу с помощью send и получать их с помощью receive . Давайте передадим сообщение текущему процессу и получим его так же.

Live Demo

send(self(), {:hello, "Hi people"})

receive do
   {:hello, msg} -> IO.puts(msg)
   {:another_case, msg} -> IO.puts("This one won't match!")
end

Когда вышеуказанная программа запущена, она дает следующий результат —

Hi people

Мы отправили сообщение текущему процессу с помощью функции send и передали его в PID self. Затем мы обработали входящее сообщение с помощью функции receive .

Когда сообщение отправляется процессу, оно сохраняется в почтовом ящике процесса . Блок получения проходит через текущий почтовый ящик процесса в поисках сообщения, соответствующего любому из заданных шаблонов. Блок приема поддерживает охрану и многие пункты, такие как case.

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

receive do
   {:hello, msg}  -> msg
after
   1_000 -> "nothing after 1s"
end

Когда вышеуказанная программа запущена, она дает следующий результат —

nothing after 1s

ПРИМЕЧАНИЕ. — Время ожидания 0 может быть задано, когда вы уже ожидаете, что сообщение будет находиться в почтовом ящике.

связи

Самая распространенная форма появления в Elixir — это функция spawn_link . Прежде чем взглянуть на пример с spawn_link, давайте разберемся, что происходит при сбое процесса.

spawn fn -> raise "oops" end

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

[error] Process #PID<0.58.00> raised an exception
** (RuntimeError) oops
   :erlang.apply/2

В журнале произошла ошибка, но процесс создания все еще выполняется. Это потому, что процессы изолированы. Если мы хотим, чтобы сбой в одном процессе распространялся на другой, нам нужно связать их. Это можно сделать с помощью функции spawn_link . Давайте рассмотрим пример, чтобы понять то же самое —

spawn_link fn -> raise "oops" end

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

** (EXIT from #PID<0.41.0>) an exception was raised:
   ** (RuntimeError) oops
      :erlang.apply/2

Если вы выполняете это в оболочке iex, то оболочка обрабатывает эту ошибку и не завершается . Но если вы запустите сначала создание файла сценария, а затем с помощью elixir <имя-файла> .exs , родительский процесс также будет остановлен из-за этого сбоя.

Процессы и ссылки играют важную роль при построении отказоустойчивых систем. В приложениях Elixir мы часто связываем наши процессы с супервизорами, которые будут определять, когда процесс умирает, и запускать новый процесс на его месте. Это возможно только потому, что процессы изолированы и по умолчанию ничего не делят. А поскольку процессы изолированы, сбой в одном процессе не приведет к сбою или повреждению состояния другого. В то время как другие языки потребуют от нас перехватывать / обрабатывать исключения; в Elixir у нас все нормально, если процессы не работают, потому что мы ожидаем, что супервизоры должным образом перезапустят наши системы.

государственный

Если вы создаете приложение, которое требует состояния, например, чтобы сохранить конфигурацию приложения, или вам нужно проанализировать файл и сохранить его в памяти, где бы вы его сохранили? Функциональность процесса Elixir может пригодиться при выполнении таких задач.

Мы можем писать процессы, которые зацикливаются, поддерживают состояние, а также отправлять и получать сообщения. В качестве примера давайте напишем модуль, который запускает новые процессы, которые работают как хранилище значений ключей, в файле с именем kv.exs .

defmodule KV do
   def start_link do
      Task.start_link(fn -> loop(%{}) end)
   end

   defp loop(map) do
      receive do
         {:get, key, caller} ->
         send caller, Map.get(map, key)
         loop(map)
         {:put, key, value} ->
         loop(Map.put(map, key, value))
      end
   end
end

Обратите внимание, что функция start_link запускает новый процесс, который запускает функцию цикла , начиная с пустой карты. Затем функция цикла ожидает сообщения и выполняет соответствующее действие для каждого сообщения. В случае сообщения : get , оно отправляет сообщение обратно вызывающей стороне и снова вызывает loop, чтобы дождаться нового сообщения. В то время как сообщение : put фактически вызывает цикл с новой версией карты, с заданным ключом и значением, сохраненным.

Давайте теперь запустим следующее —

iex kv.exs

Теперь вы должны быть в вашей оболочке iex . Чтобы проверить наш модуль, попробуйте следующее —

{:ok, pid} = KV.start_link

# pid now has the pid of our new process that is being 
# used to get and store key value pairs 

# Send a KV pair :hello, "Hello" to the process
send pid, {:put, :hello, "Hello"}

# Ask for the key :hello
send pid, {:get, :hello, self()}

# Print all the received messages on the current process.
flush()

Когда вышеуказанная программа запущена, она дает следующий результат —