文章目录

Elixir 进程:spawn() 与 send()

发布于 2026-04-15 10:23:20 · 浏览 24 次 · 评论 0 条

Elixir 进程:spawn() 与 send()

Elixir 的强大之处在于其轻量级进程模型。通过 spawn() 创建独立进程,并利用 send() 进行消息传递,是构建高并发应用的核心机制。本文将指导你如何从零开始创建进程、发送消息并处理接收逻辑。


第一阶段:创建进程

Elixir 中的所有代码都运行在进程内。最基础的进程创建方式是使用 spawn/1 函数,它接收一个匿名函数或函数引用作为参数。

  1. 打开 终端或命令行工具。

  2. 输入 iex 命令并回车,进入 Elixir 的交互式 Shell。

  3. 输入 以下代码创建一个简单的计算进程:

    spawn(fn -> 1 + 2 end)
  4. 观察 返回的结果。屏幕上会显示一个类似 #PID<0.85.0> 的标识符。这就是进程 ID(PID),用于唯一标识这个新创建的进程。

  5. 理解 进程行为:刚才创建的进程执行了 1 + 2 的计算,但由于没有输出结果,计算完成后进程立即结束并死亡。你不会看到 3 这个结果。

为了验证进程确实在运行并立即结束,我们可以让它打印信息。注意:IO.puts 会直接输出到终端,而不是作为进程的返回值。

  1. 输入 以下代码:

    spawn(fn -> IO.puts("Hello from process") end)
  2. 查看 输出。你会看到 Hello from process 被打印出来,紧接着返回了 PID。


第二阶段:发送与接收消息

进程之间是隔离的,不共享内存。它们通过异步消息传递进行通信。要实现通信,需要两个核心组件:send/2 用于发送,receive 块用于接收。

1. 接收消息

进程必须处于“等待”状态才能处理消息。我们使用 receive...end 代码块来阻塞进程,直到收到匹配的消息。

  1. 输入 以下代码尝试接收消息:

    receive do
      {:hello, msg} -> IO.puts("Received: #{msg}")
    end
  2. 注意 当前 Shell 会卡住(阻塞)。因为它正在等待一个包含 :hello 原子的消息。

  3. 按下 Ctrl + C 两次退出当前操作,或者打开一个新的终端窗口连接到同一个 Node(暂不操作,我们换个更简单的方法)。

2. 发送消息

由于 Shell 阻塞不方便操作,我们编写一个模块来创建一个独立的进程,该进程循环等待消息。

  1. 创建 一个名为 messenger.ex 的文件。

  2. 复制 并粘贴以下代码到文件中:

    defmodule Messenger do
      def loop do
        receive do
          {:hello, msg} ->
            IO.puts("Received hello: #{msg}")
            loop()
          {:bye} ->
            IO.puts("Goodbye!")
            # 不再递归调用 loop,进程正常退出
        end
      end
    end
  3. 保存 文件。

  4. 回到 IEx 终端,输入 编译命令:

    c("messenger.ex")

3. 运行交互

现在我们有了一个可以处理消息的模块。

  1. 输入 以下命令启动进程,并获取 PID:

    pid = spawn(Messenger, :loop, [])
  2. 输入 发送消息的命令:

    send(pid, {:hello, "World"})
  3. 观察 终端输出。你应该能看到 Received hello: World。由于我们在处理完消息后递归调用了 loop(),进程依然存活,可以继续接收消息。

  4. 输入 另一条消息:

    send(pid, {:hello, "Elixir"})
  5. 查看 输出,确认进程再次响应。

  6. 输入 退出命令:

    send(pid, {:bye})
  7. 观察Goodbye! 输出。此时进程已结束。

  8. 尝试 再次发送消息:

    send(pid, {:hello, "Again"})

    此时不会有任何输出,因为进程已经死亡,消息被发送到了“死信箱”。


第三阶段:理解消息传递流程

为了更直观地理解 spawnsendreceive 之间的协作关系,以下流程描述了完整的生命周期:

  1. 调用者使用 spawn 启动目标进程。
  2. 目标进程执行函数,进入 receive 块并挂起(等待)。
  3. 调用者使用 send 向目标 PID 发送消息。
  4. 操作系统将消息投递到目标进程的邮箱。
  5. 目标进程匹配消息模式,执行对应的代码体。

如果上述步骤难以在脑海中构建,可以参考下面的序列图。它展示了客户端如何与服务器进程进行交互。

sequenceDiagram autonumber participant Client as "调用者进程" participant Server as "Server进程 (PID)" Client->>Server: "spawn(Module, :loop, [])" activate Server Server->>Server: "执行 loop 函数" Note over Server: "进入 receive 块
(挂起等待)" Client->>Server: "send(PID, {:hello, msg})" Note over Client,Server: "消息投递到 Server 邮箱" Server->>Server: "匹配模式 {:hello, msg}" Server->>Server: "IO.puts(...)" Server->>Server: "递归调用 loop()" Note over Server: "再次进入 receive 块
(等待下次消息)"

第四阶段:保持状态

在 Elixir 中,进程本身不存储变量,但可以通过在递归调用时传递参数来模拟“状态”。我们将改造 Messenger 模块,让它记住总共收到了多少条消息。

  1. 编辑 messenger.ex 文件,替换 为以下代码:

    defmodule Counter do
      def loop(count) do
        receive do
          {:increment} ->
            new_count = count + 1
            IO.puts("Current count: #{new_count}")
            loop(new_count)
    
          {:show} ->
            IO.puts("State is: #{count}")
            loop(count)
        end
      end
    end
  2. 保存 文件。

  3. 在 IEx 中 重新编译:

    c("counter.ex")
  4. 启动 新的进程,初始状态设为 0

    pid = spawn(Counter, :loop, [0])
  5. 发送 增加指令多次:

    send(pid, {:increment})
    send(pid, {:increment})

    输出应依次显示 12

  6. 发送 查看状态指令:

    send(pid, {:show})

    输出将显示 State is: 2

这个机制实现了状态的封装:count 变量只存在于进程的递归调用栈中,外部只能通过发送消息来改变它,这正是 Actor 模型的精髓。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文