Spis treści
Procesy
Cały kod Elixira
działa wewnątrz procesów, które są od siebie odizolowane i działają współbieżnie tudzież równolegle, a komunikują się poprzez przekazywanie wiadomości.
Procesy te nie mogą być mylone z procesami w systemie operacyjnym.
Te są niesamowicie lekkie w rozumieniu zużycia procesora (ang. CPU
) jak i pamięci (nawet w porównaniu do wątków w innych językach programowania).
Sprawdźmy więc jak wyglądają podstawowe konstrukty w tym obszarze (tworzenia procesów) oraz jak wygląda przekazywanie wiadomości między nimi.
Tworzenie procesów
Wcześniej stworzyłem funkcję do generowania ID na potrzeby zamówień Order.Helpers.generate_id/1
:
Order.Helpers.generate_id
Aby uruchomić ją jako proces, wystarczy użyć polecenia spawn
, które zwraca PID
procesu:
gen_id_pid = spawn(Order.Helpers, :generate_id, [])
Aby sprawdzić czy proces jeszcze żyje możemy sprawdzić z pomocą:
Process.alive?(gen_id_pid)
Aby sprawdzić PID aktualnego procesu, używamy self/0
:
self()
Możemy też zespawnować
proces do zwykłej anonimowej funkcji:
fnxp = fn -> IO.inspect("I am the lucky phrase #{Order.Helpers.generate_id()}") end
fnxp_process = spawn(fnxp)
Process.alive?(fnxp_process)
Przekazywanie wiadomości
Wiadomości w przypadku procesów wysyłamy z pomocą send/2
, a odbieramy przy pomocy receive/1
:
parent_pid = self()
spawn(fn -> send(parent_pid, {:hi, self()}) end)
receive do
{:hi, pid} -> "Got a #{:hi} from #{inspect pid}"
end
Inny przykład
Prosty producent/konsumer
Innym ciekawym przykładem może być prosty producent/konsumer
(ang. pub/sub
), używający czystego Elixira:
defmodule Producer do
def start_link do
spawn_link(fn -> loop() end)
end
defp loop do
receive do
{:produce, consumer_pid} ->
produced_item = :rand.uniform(1000)
send(consumer_pid, {:produced_item, produced_item})
loop()
end
end
end
defmodule Consumer do
def start_link do
spawn_link(fn -> loop() end)
end
defp loop do
receive do
{:produced_item, item} ->
IO.puts("Consumed item: #{item}")
loop()
end
end
end
defmodule ProducerConsumerExample do
def run do
consumer_pid = Consumer.start_link()
producer_pid = Producer.start_link()
send(producer_pid, {:produce, consumer_pid})
send(producer_pid, {:produce, consumer_pid})
:timer.sleep(1000)
send(producer_pid, {:produce, consumer_pid})
send(producer_pid, {:produce, consumer_pid})
:timer.sleep(2000)
end
end
ProducerConsumerExample.run()
Procesy zlinkowane
Bardzo rzadko jednak, używamy procesów w takiej jak powyżej formie - no chyba, że chcemy aby coś się wykonało w tle i aby ew. błąd osiągnięty tam, nie miał jakiegokolwiek wpływu na to co robi Nasz główny kod.
Jeżeli chcemy aby błąd w jednym procesie miał wpływ na następny - powinniśmy je zlinkować z pomocą spawn_link/1
.
Task
Kolejnym ciekawym zagadnieniem jest istnienie Task
‘ów w Elixirze, które zamiast spawn/1
oraz spawn_link/1
udostępniają Task.start/1
oraz Task.start_link/1
, które w domyślne zamiast samego PID zwracają: {:ok, pid}
.
To umożliwia używanie task
‘ów w tzw supervision trees
, udostępniając dodatkowo programiście Task.async/1
oraz Task.await/1
.
Natomiast bardzo rzadko będziecie tak naprawdę używać funkcjonalności procesów i tasków bezpośrednio.
Agent
Ku temu stworzono Agents
, które jako abstrakcje w okół stanu wchodzą w skład mechanizmów OTP
.
Dla przykładu:
{:ok, pid} = Agent.start_link(fn -> %{} end) # {:ok, #PID<0.12.0>}
Agent.update(pid, fn map -> Map.put(map, :hello, :world) end)
Agent.get(pid, fn map -> Map.get(map, :hello) end)
Możemy też tego agenta oczywiście zatrzymać:
Agent.stop(pid)
Dodatkowo możemy nadać mu nazwę z pomocą :atom'u
:
{:ok, store} = Agent.start_link(fn -> %{} end, name: :store) # {:ok, #PID<0.43.0>}
Natomiast… Nie jest to najlepsza praktyka, zwłaszcza w programach, które mogą mięć setki agentów. Po drugie atomy nie są odśmiecane, więc możemy dosyć sprawnie zapełnić pamięć mając np. miliony atomów tworzonych np. per proces.
W tym przypadku lepszym rozwiązaniem będzie użycie GenServer
oraz Supervisor
‘a.
Podsumowanie
To by było na tyle w tej części (krótszej niż pozostałe), kontynuacja oczywiście znajdzie się w kolejnym artykule.
Natomiast wiele ciekawych rzeczy nt. agent’ów, GenServer oraz Supervisor, znajdziecie także: tutaj.