Elixir 进程:spawn() 与 send()
Elixir 的强大之处在于其轻量级进程模型。通过 spawn() 创建独立进程,并利用 send() 进行消息传递,是构建高并发应用的核心机制。本文将指导你如何从零开始创建进程、发送消息并处理接收逻辑。
第一阶段:创建进程
Elixir 中的所有代码都运行在进程内。最基础的进程创建方式是使用 spawn/1 函数,它接收一个匿名函数或函数引用作为参数。
-
打开 终端或命令行工具。
-
输入
iex命令并回车,进入 Elixir 的交互式 Shell。 -
输入 以下代码创建一个简单的计算进程:
spawn(fn -> 1 + 2 end) -
观察 返回的结果。屏幕上会显示一个类似
#PID<0.85.0>的标识符。这就是进程 ID(PID),用于唯一标识这个新创建的进程。 -
理解 进程行为:刚才创建的进程执行了
1 + 2的计算,但由于没有输出结果,计算完成后进程立即结束并死亡。你不会看到3这个结果。
为了验证进程确实在运行并立即结束,我们可以让它打印信息。注意:IO.puts 会直接输出到终端,而不是作为进程的返回值。
-
输入 以下代码:
spawn(fn -> IO.puts("Hello from process") end) -
查看 输出。你会看到
Hello from process被打印出来,紧接着返回了 PID。
第二阶段:发送与接收消息
进程之间是隔离的,不共享内存。它们通过异步消息传递进行通信。要实现通信,需要两个核心组件:send/2 用于发送,receive 块用于接收。
1. 接收消息
进程必须处于“等待”状态才能处理消息。我们使用 receive...end 代码块来阻塞进程,直到收到匹配的消息。
-
输入 以下代码尝试接收消息:
receive do {:hello, msg} -> IO.puts("Received: #{msg}") end -
注意 当前 Shell 会卡住(阻塞)。因为它正在等待一个包含
:hello原子的消息。 -
按下
Ctrl + C两次退出当前操作,或者打开一个新的终端窗口连接到同一个 Node(暂不操作,我们换个更简单的方法)。
2. 发送消息
由于 Shell 阻塞不方便操作,我们编写一个模块来创建一个独立的进程,该进程循环等待消息。
-
创建 一个名为
messenger.ex的文件。 -
复制 并粘贴以下代码到文件中:
defmodule Messenger do def loop do receive do {:hello, msg} -> IO.puts("Received hello: #{msg}") loop() {:bye} -> IO.puts("Goodbye!") # 不再递归调用 loop,进程正常退出 end end end -
保存 文件。
-
回到 IEx 终端,输入 编译命令:
c("messenger.ex")
3. 运行交互
现在我们有了一个可以处理消息的模块。
-
输入 以下命令启动进程,并获取 PID:
pid = spawn(Messenger, :loop, []) -
输入 发送消息的命令:
send(pid, {:hello, "World"}) -
观察 终端输出。你应该能看到
Received hello: World。由于我们在处理完消息后递归调用了loop(),进程依然存活,可以继续接收消息。 -
输入 另一条消息:
send(pid, {:hello, "Elixir"}) -
查看 输出,确认进程再次响应。
-
输入 退出命令:
send(pid, {:bye}) -
观察 到
Goodbye!输出。此时进程已结束。 -
尝试 再次发送消息:
send(pid, {:hello, "Again"})此时不会有任何输出,因为进程已经死亡,消息被发送到了“死信箱”。
第三阶段:理解消息传递流程
为了更直观地理解 spawn、send 和 receive 之间的协作关系,以下流程描述了完整的生命周期:
- 调用者使用
spawn启动目标进程。 - 目标进程执行函数,进入
receive块并挂起(等待)。 - 调用者使用
send向目标 PID 发送消息。 - 操作系统将消息投递到目标进程的邮箱。
- 目标进程匹配消息模式,执行对应的代码体。
如果上述步骤难以在脑海中构建,可以参考下面的序列图。它展示了客户端如何与服务器进程进行交互。
(挂起等待)" 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 模块,让它记住总共收到了多少条消息。
-
编辑
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 -
保存 文件。
-
在 IEx 中 重新编译:
c("counter.ex") -
启动 新的进程,初始状态设为
0:pid = spawn(Counter, :loop, [0]) -
发送 增加指令多次:
send(pid, {:increment}) send(pid, {:increment})输出应依次显示
1和2。 -
发送 查看状态指令:
send(pid, {:show})输出将显示
State is: 2。
这个机制实现了状态的封装:count 变量只存在于进程的递归调用栈中,外部只能通过发送消息来改变它,这正是 Actor 模型的精髓。

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