Пр

Процеси у Elixir

5 вправ

Про концепцію Процеси

У Elixir увесь код виконується всередині процесів. Процеси в Elixir:

  • Не слід плутати із системними процесами.
  • Легковагі.
  • Мають конкретні сфери застосування. Вони можуть:
  • Цілком нормально мати застосунок на Elixir, який запускає сотні процесів, але також і такий, що взагалі не створює нових процесів явно, особливо якщо це бібліотека.

Створення процесів

Типово функція виконується в тому самому процесі, з якого її викликали. Коли потрібно явно запустити певну функцію в новому процесі, скористаймося spawn:

  • spawn/1 приймає функцію, яку він виконає безпосередньо.

    spawn(fn -> 2 + 2 end)
    # => #PID<0.125.0>
    
  • spawn/3 приймає функцію, яку він виконає за назвою модуля, назвою функції (як атом) і масивом аргументів, що передаються цій функції.

    spawn(String, :split, ["hello there", " "])
    # => #PID<0.113.0>
    
    • Цю трійку даних часто називають MFA: Module, Function, Arguments.
  • Процес завершується, щойно його функція закінчує виконання.

  • Перевірити, чи процес усе ще живий (виконується), можна за допомогою Process.alive?/1:

    pid = spawn(fn -> 2 + 2 end)
    Process.alive?(pid)
    # => false
    

Повідомлення

Процеси не обмінюються інформацією безпосередньо один з одним. Щоб поділитися даними, процеси надсилають повідомлення. Цю модель конкурентності називають модель акторів.

  • Надсилати повідомлення процесу можна за допомогою send/2.

    send(pid, :hello)
    
    • Повідомлення потрапляє до поштової скриньки отримувача в тому порядку, у якому їх надіслано.
    • send не перевіряє, чи повідомлення отримано, і чи отримувач усе ще живий.
  • Повідомлення може бути будь-якого типу.

  • Отримати повідомлення, надіслане поточному процесу, можна за допомогою receive/1.

    • Повідомлення потрібно зіставляти зі шаблоном.
    • receive чекає, поки в поштовій скриньці процесу зʼявиться повідомлення, що відповідає будь-якому із заданих шаблонів.
      • Типово він чекає нескінченно, але можна задати тайм-аут за допомогою блоку after.
    • Прочитані повідомлення видаляються з поштової скриньки процесу. Непрочитані повідомлення будуть залишатися там нескінченно.
      • Завжди додаваймо до receive/1 універсальну умову _, щоб уникнути вичерпання памʼяті через накопичення непрочитаних повідомлень.
    receive do
      {:ping, sender_pid} -> send(sender_pid, :pong)
      _ -> nil
    after
      5000 ->
        {:error, "No message in 5 seconds"}
    end
    

Цикл отримання

Якщо потрібно отримати більше ніж одне повідомлення, receive/1 доведеться викликати рекурсивно. Поширений прийом - реалізувати рекурсивну функцію, наприклад із назвою loop, яка викликає receive/1, щось робить із повідомленням, а потім викликає саму себе, щоб чекати на нові повідомлення. Якщо потрібно перенести якийсь стан з одного виклику receive/1 до іншого, це можна зробити, передавши аргумент у ту функцію loop.

def loop(state) do
  receive do
    :increment_by_one ->
      loop(state + 1)

    {:report_state, sender_pid} ->
      send(sender_pid, state)
      loop(state)

    :stop ->
      nil

    _ ->
      loop(state)
  end
end

На практиці цей підхід рідко використовують напряму. Elixir пропонує абстракції конкурентності, як-от модуль Agent або поведінку GenServer, які обидві спираються на цикл отримання. Але вкрай важливо розуміти ці основи, щоб ефективно користуватися абстракціями.

Редагувати через GitHub Посилання відкривається в новому вікні або вкладці

Вивчити концепцію Процеси

Практика заблокована

Розблокуйте ще 4 вправи, щоб практикувати концепцію Процеси