进程

进程 属于 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
    

消息

进程之间不直接共享信息。进程通过_发送消息_来共享数据。这种并发模式叫做 Actor 模型。

  • 用 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 个练习即可练习 进程