Как создать процесс с помощью 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

Этот код создаёт сервер для сложения чисел, который может работать вечно.