📜  Elixir-流程

📅  最后修改于: 2020-11-04 08:29:13             🧑  作者: Mango


 

在Elixir中,所有代码都在进程内部运行。进程彼此隔离,并发运行,并通过消息传递进行通信。 Elixir的进程不应与操作系统进程混淆。 Elixir中的进程在内存和CPU方面极其轻巧(与许多其他编程语言中的线程不同)。因此,同时运行成千上万个进程的情况并不少见。

在本章中,我们将学习产生新进程以及在不同进程之间发送和接收消息的基本构造。

生成函数

创建新进程的最简单方法是使用spawn函数。衍生工具接受将在新进程中运行的函数。例如-

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

运行上述程序时,将产生以下结果-

false

生成函数的返回值是PID。这是该过程的唯一标识符,因此,如果您在PID之上运行代码,它将有所不同。如本例所示,当我们检查进程是否仍然有效时,该进程已死。这是因为该进程将在完成给定函数后立即退出。

如前所述,所有Elixir代码都在进程内部运行。如果您运行自我函数,您将看到当前会话的PID-

pid = self
 
Process.alive?(pid)

当上述程序运行时,它产生以下结果-

true

讯息传递

我们可以将消息发送到与发送的过程,并与收到接收它们。让我们将消息传递给当前进程,并在同一进程上接收它。

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

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

运行上述程序时,将产生以下结果-

Hi people

我们使用send函数向当前进程发送了一条消息,并将其传递给self的PID。然后,我们使用接收函数处理传入的消息。

当消息发送到流程时,该消息存储在流程邮箱中。接收块通过当前进程邮箱搜索与任何给定模式匹配的消息。接收块支持防护和许多子句,例如case。

如果邮箱中没有与任何模式匹配的消息,则当前过程将等待,直到匹配的消息到达为止。也可以指定超时。例如,

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

运行上述程序时,将产生以下结果-

nothing after 1s

–当您已经希望邮件出现在邮箱中时,可以将超时设置为0。

链接

实际上,Elixir中最常见的生成形式是通过spawn_link函数。在使用spawn_link查看示例之前,让我们了解一个进程失败时会发生什么。

spawn fn -> raise "oops" end

当上述程序运行时,会产生以下错误-

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

它记录了一个错误,但生成过程仍在运行。这是因为进程是隔离的。如果我们希望一个过程中的故障传播到另一个过程中,则需要将它们链接起来。这可以通过spawn_link函数。让我们考虑一个例子来理解相同-

spawn_link fn -> raise "oops" end

当上述程序运行时,会产生以下错误-

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

如果您在iex Shell中运行此程序,则Shell处理此错误并且不会退出。但是,如果先运行脚本文件然后使用elixir .exs运行,由于此失败,父进程也将被关闭。

在构建容错系统时,过程和链接起着重要作用。在Elixir应用程序中,我们通常将流程链接到主管,主管将检测流程何时死亡,并在其位置开始新流程。这仅是可能的,因为进程是隔离的,并且默认情况下不共享任何内容。而且由于进程是隔离的,所以一个进程中的任何故障都不会崩溃或破坏另一个状态。而其他语言将要求我们捕获/处理异常;在Elixir中,让进程失败实际上是可以的,因为我们希望主管可以正确地重新启动系统。

例如,如果要构建需要状态的应用程序以保留应用程序配置,或者需要解析文件并将其保存在内存中,则将文件存储在哪里?做这些事情时,Elixir的过程功能会派上用场。

我们可以编写无限循环,维护状态以及发送和接收消息的过程。例如,让我们编写一个模块来启动新进程,这些新进程将作为键值存储在名为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

请注意, start_link函数启动一个新进程,该进程从空映射开始运行循环函数。然后,循环函数等待消息并为每个消息执行适当的操作。在:get消息的情况下,它将消息发送回调用方并再次调用循环,以等待新消息。 :put消息实际上使用映射的新版本调用循环,并存储了给定的键和值。

现在让我们运行以下命令-

iex kv.exs

现在,您应该在iex shell中。要测试我们的模块,请尝试以下操作-

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

运行上述程序时,将产生以下结果-

"Hello"