Elixir - Processus

Dans Elixir, tout le code s'exécute à l'intérieur des processus. Les processus sont isolés les uns des autres, s'exécutent simultanément et communiquent via la transmission de messages. Les processus d'Elixir ne doivent pas être confondus avec les processus du système d'exploitation. Les processus d'Elixir sont extrêmement légers en termes de mémoire et de processeur (contrairement aux threads de nombreux autres langages de programmation). Pour cette raison, il n'est pas rare que des dizaines, voire des centaines de milliers de processus s'exécutent simultanément.

Dans ce chapitre, nous allons découvrir les constructions de base pour générer de nouveaux processus, ainsi que pour envoyer et recevoir des messages entre différents processus.

La fonction Spawn

Le moyen le plus simple de créer un nouveau processus est d'utiliser le spawnfonction. lespawnaccepte une fonction qui sera exécutée dans le nouveau processus. Par exemple -

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

Lorsque le programme ci-dessus est exécuté, il produit le résultat suivant -

false

La valeur de retour de la fonction spawn est un PID. Il s'agit d'un identifiant unique pour le processus et donc si vous exécutez le code au-dessus de votre PID, ce sera différent. Comme vous pouvez le voir dans cet exemple, le processus est mort lorsque nous vérifions s'il est vivant. En effet, le processus se terminera dès qu'il aura terminé d'exécuter la fonction donnée.

Comme déjà mentionné, tous les codes Elixir s'exécutent à l'intérieur des processus. Si vous exécutez la fonction auto, vous verrez le PID de votre session en cours -

pid = self
 
Process.alive?(pid)

Lorsque le programme ci-dessus est exécuté, il produit le résultat suivant -

true

Message passant

Nous pouvons envoyer des messages à un processus avec send et recevez-les avec receive. Passons un message au processus actuel et recevons-le sur le même.

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

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

Lorsque le programme ci-dessus est exécuté, il produit le résultat suivant -

Hi people

Nous avons envoyé un message au processus actuel en utilisant la fonction d'envoi et l'avons passé au PID de self. Ensuite, nous avons traité le message entrant en utilisant lereceive fonction.

Lorsqu'un message est envoyé à un processus, le message est stocké dans le process mailbox. Le bloc de réception parcourt la boîte aux lettres de processus en cours à la recherche d'un message correspondant à l'un des modèles donnés. Le bloc de réception prend en charge les gardes et de nombreuses clauses, telles que case.

S'il n'y a aucun message dans la boîte aux lettres correspondant à l'un des modèles, le processus en cours attendra jusqu'à ce qu'un message correspondant arrive. Un délai d'expiration peut également être spécifié. Par exemple,

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

Lorsque le programme ci-dessus est exécuté, il produit le résultat suivant -

nothing after 1s

NOTE - Un délai d'expiration de 0 peut être donné lorsque vous vous attendez déjà à ce que le message soit dans la boîte aux lettres.

Liens

La forme la plus courante de frai dans Elixir est en fait via spawn_linkfonction. Avant de jeter un œil à un exemple avec spawn_link, voyons ce qui se passe lorsqu'un processus échoue.

spawn fn -> raise "oops" end

Lorsque le programme ci-dessus est exécuté, il produit l'erreur suivante -

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

Il a enregistré une erreur mais le processus de frai est toujours en cours. C'est parce que les processus sont isolés. Si nous voulons que l'échec d'un processus se propage à un autre, nous devons les lier. Cela peut être fait avec lespawn_linkfonction. Prenons un exemple pour comprendre la même chose -

spawn_link fn -> raise "oops" end

Lorsque le programme ci-dessus est exécuté, il produit l'erreur suivante -

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

Si vous exécutez ceci dans iexshell alors le shell gère cette erreur et ne se ferme pas. Mais si vous exécutez en créant d'abord un fichier de script, puis en utilisantelixir <file-name>.exs, le processus parent sera également interrompu en raison de cet échec.

Les processus et les liens jouent un rôle important lors de la création de systèmes tolérants aux pannes. Dans les applications Elixir, nous lions souvent nos processus à des superviseurs qui détectent quand un processus meurt et démarrent un nouveau processus à sa place. Cela n'est possible que parce que les processus sont isolés et ne partagent rien par défaut. Et comme les processus sont isolés, il n'y a aucun moyen qu'un échec dans un processus se bloque ou corrompe l'état d'un autre. Alors que d'autres langages nous obligeront à attraper / gérer les exceptions; dans Elixir, nous sommes d'accord pour laisser les processus échouer parce que nous attendons des superviseurs qu'ils redémarrent correctement nos systèmes.

Etat

Si vous créez une application qui nécessite un état, par exemple, pour conserver la configuration de votre application, ou si vous devez analyser un fichier et le conserver en mémoire, où le stockeriez-vous? La fonctionnalité de processus d'Elixir peut être utile lorsque vous faites de telles choses.

Nous pouvons écrire des processus qui bouclent indéfiniment, maintiennent l'état et envoient et reçoivent des messages. À titre d'exemple, écrivons un module qui démarre de nouveaux processus qui fonctionnent comme un magasin clé-valeur dans un fichier nommé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

Notez que le start_link La fonction démarre un nouveau processus qui exécute le loopfonction, en commençant par une carte vide. leloopLa fonction attend ensuite les messages et exécute l'action appropriée pour chaque message. Dans le cas d'un:getmessage, il renvoie un message à l'appelant et appelle à nouveau en boucle, pour attendre un nouveau message. Tandis que le:put le message invoque en fait loop avec une nouvelle version de la carte, avec la clé et la valeur données stockées.

Examinons maintenant ce qui suit -

iex kv.exs

Maintenant tu devrais être dans ton iexcoquille. Pour tester notre module, essayez ce qui suit -

{: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()

Lorsque le programme ci-dessus est exécuté, il produit le résultat suivant -

"Hello"