当前位置: 首页 > 文档资料 > Elixir 中文教程 >

进程( Processes)

优质
小牛编辑
135浏览
2023-12-01

在Elixir中,所有代码都在进程内运行。 进程彼此隔离,彼此并发运行并通过消息传递进行通信。 Elixir的流程不应与操作系统流程混淆。 Elixir中的进程在内存和CPU方面非常轻量级(与许多其他编程语言中的线程不同)。 因此,同时运行数十甚至数十万个进程并不罕见。

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

产卵功能

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

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

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

false

spawn函数的返回值是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 of PID。 然后我们使用receive函数处理传入的消息。

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

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

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

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

nothing after 1s

NOTE - 当您希望邮件在邮箱中时,可以给出超时0。

Links

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 《file-name》.exs ,那么父进程也会因为这个失败而被关闭。

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

State

例如,如果要构建一个需要状态的应用程序来保持应用程序配置,或者需要解析文件并将其保存在内存中,那么您将在何处存储它? 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函数启动一个运行loop函数的新进程,从空映射开始。 然后loop函数等待消息并对每条消息执行适当的操作。 在a :get消息的情况下,它将消息发送回调用者并再次调用循环,以等待新消息。 虽然:put消息实际上使用新版本的地图调用loop ,但存储了给定的键和值。

现在让我们运行以下内容 -

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"