Как создать процесс с помощью spawn?
В Elixir создание процесса осуществляется при помощи функции spawn/1, spawn/3 или модифицированных версий с модулем и функцией. Каждый вызов spawn создаёт новый независимый процесс, работающий параллельно с остальными. Эти процессы работают на виртуальной машине BEAM и не используют потоки или процессы операционной системы — это lightweight-processes, встроенные в архитектуру Elixir/Erlang.
Использование spawn/1
Форма spawn(fn -> ... end) запускает анонимную функцию:
spawn(fn -> IO.puts("Привет из нового процесса!") end)
Здесь создаётся процесс, выполняющий IO.puts. Возвращается PID процесса.
Возврат значения
Важно: результат работы функции не возвращается вызывающему процессу. Процесс завершается независимо, и чтобы получить данные, нужно использовать send/receive.
Пример:
pid = spawn(fn ->
send(self(), {:done, 42})
end)
receive do
{:done, result} -> IO.puts("Результат: #{result}")
end
Использование spawn/3
Форма spawn(Module, :function_name, [args]) позволяет запустить именованную функцию из модуля:
defmodule Worker do
def run(name) do
IO.puts("Процесс выполняется: #{name}")
end
end
spawn(Worker, :run, \["Алексей"\])
Создаётся процесс, который вызывает Worker.run("Алексей").
Получение PID и взаимодействие
При запуске spawn возвращается PID нового процесса. Его можно сохранить и использовать:
pid = spawn(fn ->
receive do
{:ping, from} -> send(from, :pong)
end
end)
send(pid, {:ping, self()})
receive do
:pong -> IO.puts("Получен ответ: pong")
end
Цикл обработки сообщений (loop)
Чтобы процесс работал постоянно, используют рекурсию:
defmodule Echo do
def listen do
receive do
{:echo, msg, sender} ->
send(sender, {:ok, msg})
end
listen()
end
end
pid = spawn(Echo, :listen, \[\])
send(pid, {:echo, "Привет", self()})
receive do
{:ok, response} -> IO.puts("Ответ: #{response}")
end
Процесс Echo обрабатывает сообщения и сам себя перезапускает.
Завершение процесса
Процесс завершится, если:
-
функция выполнена полностью;
-
в receive не приходит сообщение (зависает);
-
выброшено исключение.
Завершённый процесс может быть нормально завершён (:normal) или с ошибкой, в зависимости от поведения.
Пример: создание двух процессов
defmodule Counter do
def count(from, to) do
Enum.each(from..to, fn i ->
IO.puts("#{self()} => #{i}")
end)
end
end
spawn(Counter, :count, \[1, 5\])
spawn(Counter, :count, \[100, 105\])
Вывод может быть перемешан, так как процессы работают параллельно.
Использование spawn_link/1 и spawn_monitor/1
-
spawn_link создаёт связь между текущим и новым процессом. Если один падает — другой тоже завершится.
-
spawn_monitor отслеживает завершение, но не падает при ошибке.
spawn_link(fn -> raise "Ошибка" end) # текущий процесс тоже упадёт
{pid, ref} = spawn_monitor(fn -> 1 + 1 end)
receive do
{:DOWN, ^ref, :process, ^pid, \_reason} ->
IO.puts("Процесс завершился")
end
Пример со сложной логикой
defmodule MathWorker do
def run do
receive do
{:sum, a, b, caller} ->
send(caller, {:result, a + b})
end
run()
end
end
pid = spawn(MathWorker, :run, \[\])
send(pid, {:sum, 5, 10, self()})
receive do
{:result, result} -> IO.puts("Сумма: #{result}")
end
Этот код создаёт сервер для сложения чисел, который может работать вечно.