文章目录

Erlang 进程:spawn() 与消息传递

发布于 2026-04-02 15:15:14 · 浏览 7 次 · 评论 0 条

Erlang 进程:spawn() 与消息传递

Erlang 的核心优势在于其轻量级并发模型,每个“进程”都是独立运行的执行单元,不共享内存,仅通过消息传递通信。这种设计天然避免了锁竞争和数据竞争问题,非常适合构建高可用、高并发的系统。掌握 spawn() 和消息传递机制,是编写 Erlang 并发程序的第一步。


创建一个新进程

在 Erlang 中,调用 spawn/1spawn/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(消息队列)、statusmemory 等。
  • 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 内存),可轻松创建数十万个。不要像操作系统线程那样吝啬使用。

评论 (0)

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

扫一扫,手机查看

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