Erlang 进程:spawn() 与消息传递
Erlang 的核心优势在于其轻量级并发模型,每个“进程”都是独立运行的执行单元,不共享内存,仅通过消息传递通信。这种设计天然避免了锁竞争和数据竞争问题,非常适合构建高可用、高并发的系统。掌握 spawn() 和消息传递机制,是编写 Erlang 并发程序的第一步。
创建一个新进程
在 Erlang 中,调用 spawn/1 或 spawn/3 函数即可启动一个新进程。
spawn(Fun):传入一个匿名函数(fun)。spawn(Module, Function, Arguments):传入模块名、函数名和参数列表。
使用匿名函数创建进程
编写如下代码并保存为 hello.erl:
-module(hello).
-export([start/0, greet/0]).
start() ->
Pid = spawn(fun greet/0),
io:format("新进程 PID 是 ~p~n", [Pid]).
greet() ->
io:format("你好,我是进程 ~p!~n", [self()]).
编译该模块:
erlc hello.erl
启动 Erlang shell 并运行:
1> hello:start().
新进程 PID 是 <0.123.0>
你好,我是进程 <0.123.0>!
ok
注意:输出顺序可能不同,因为主进程和新进程并发执行。
使用模块-函数-参数形式
修改 start/0 函数如下:
start() ->
Pid = spawn(?MODULE, greet, []),
io:format("新进程 PID 是 ~p~n", [Pid]).
效果完全相同。?MODULE 是宏,代表当前模块名 hello。
向进程发送消息
Erlang 进程之间通过 ! 操作符发送消息。语法为 目标进程 ! 消息内容。
扩展上面的例子,让主进程向新进程发一条消息:
-module(hello).
-export([start/0, loop/0]).
start() ->
Pid = spawn(?MODULE, loop, []),
Pid ! {hello, "主进程"},
timer:sleep(100). % 等待消息处理完成
loop() ->
receive
{hello, Msg} ->
io:format("收到消息: ~s~n", [Msg]),
loop(); % 继续监听下一条消息
stop ->
io:format("进程退出~n")
end.
关键点:
- 使用
receive ... end结构接收消息。 receive会阻塞,直到匹配到某条消息。- 匹配规则基于 Erlang 的模式匹配:
{hello, Msg}能匹配形如{hello, "xxx"}的元组。 - 调用
loop()实现循环监听,否则进程处理完一条消息就结束了。
运行后输出:
收到消息: 主进程
进程间双向通信
实际应用中,常需要请求-响应模式:A 发消息给 B,B 处理后回传结果。
实现方法:A 在消息中附带自己的 PID,B 收到后用该 PID 回复。
编写一个简单的计算器进程:
-module(calc).
-export([start/0, request/2, server/0]).
start() ->
spawn(?MODULE, server, []).
request(Pid, {Op, A, B}) ->
Pid ! {self(), {Op, A, B}},
receive
{result, Value} -> Value
after 5000 ->
timeout
end.
server() ->
receive
{From, {add, X, Y}} ->
From ! {result, X + Y},
server();
{From, {mul, X, Y}} ->
From ! {result, X * Y},
server();
stop ->
ok
end.
使用方式:
1> c(calc).
{ok,calc}
2> Pid = calc:start().
<0.125.0>
3> calc:request(Pid, {add, 3, 5}).
8
4> calc:request(Pid, {mul, 4, 7}).
28
说明:
request/2先发消息{self(), {Op, A, B}},其中self()是调用者的 PID。server/0收到后,用From ! {result, ...}回复。receive ... after ...设置 5 秒超时,防止无限等待。
查看进程状态与调试
Erlang 提供内置工具观察进程行为。
在 shell 中执行:
1> Pid = spawn(fun() -> timer:sleep(10000) end).
<0.126.0>
2> erlang:process_info(Pid, messages).
{messages,[]}
3> Pid ! hello.
hello
4> erlang:process_info(Pid, messages).
{messages,[hello]}
5> exit(Pid, kill).
true
6> erlang:process_info(Pid, messages).
undefined
关键函数:
erlang:process_info(Pid, Item):获取进程信息。- 常用
Item包括messages(消息队列)、status、memory等。 exit(Pid, Reason)可强制终止进程。
若需查看所有进程,运行:
> i(). % 注意:这是 shell 命令,不是 Erlang 函数
输出表格包含 PID、初始函数、内存使用、消息队列长度等。
错误处理与链接
默认情况下,一个进程崩溃不会影响其他进程。但有时希望父子进程共存亡。
使用 spawn_link/1 替代 spawn/1,可建立“链接”(link):
bad_process() ->
spawn_link(fun() -> 1/0 end). % 故意触发错误
运行后,不仅子进程因除零错误退出,父进程也会因接收到 {'EXIT', Pid, Reason} 消息而崩溃。
若想捕获退出信号而不崩溃,设置进程为“处理退出信号”模式:
safe_start() ->
process_flag(trap_exit, true),
spawn_link(fun() -> 1/0 end),
receive
{'EXIT', _Pid, Reason} ->
io:format("子进程因 ~p 退出~n", [Reason])
end.
process_flag(trap_exit, true) 会将链接进程的退出信号转为普通消息,避免自身崩溃。
最佳实践总结
| 场景 | 推荐做法 |
|---|---|
| 启动简单任务 | 使用 spawn(fun ... end) |
| 启动模块函数 | 使用 spawn(Mod, Fun, Args) |
| 需要监控子进程 | 使用 spawn_link/1 + trap_exit |
| 请求-响应通信 | 消息中携带调用者 PID |
| 避免僵尸进程 | 在 receive 后递归调用自身形成循环 |
| 防止无限阻塞 | receive 添加 after Timeout 分支 |
记住:Erlang 进程非常轻量(约 2KB 内存),可轻松创建数十万个。不要像操作系统线程那样吝啬使用。

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