【IT168 技术文档】下面的例子中,我们创建了两个进程,其中一个重复向另一个发送消息。
-module(tut15). -export([start/0, ping/2, pong/0]). ping(0, Pong_PID) -> Pong_PID ! finished, io:format("ping finished~n", []); ping(N, Pong_PID) -> Pong_PID ! {ping, self()}, receive pong -> io:format("Ping received pong~n", []) end, ping(N - 1, Pong_PID). pong() -> receive finished -> io:format("Pong finished~n", []); {ping, Ping_PID} -> io:format("Pong received ping~n", []), Ping_PID ! pong, pong() end. start() -> Pong_PID = spawn(tut15, pong, []), spawn(tut15, ping, [3, Pong_PID]). 1> c(tut15). {ok,tut15} 2> tut15: start(). <0.36.0> Pong received ping Ping received pong Pong received ping Ping received pong Pong received ping Ping received pong ping finished Pong finished
函数start首先创建了一个进程,我们叫它做“pong”:
Pong_PID = spawn(tut15, pong, [])
这个进程执行tut15:pong()。Pong_PID是这个进程“pong” 的标识符。函数start现在要创建另一个进程“ping”了。
spawn(tut15, ping, [3, Pong_PID]),
这个进程执行:
tut15:ping(3, Pong_PID)
<0.36.0> 是函数start的返回值。
进程“pong”现在做:
receive finished -> io:format("Pong finished~n", []); {ping, Ping_PID} -> io:format("Pong received ping~n", []), Ping_PID ! pong, pong() end.
receive 关键词被用来让进程等待从其他进程发来的消息,格式如下:
receive pattern1 -> actions1; pattern2 -> actions2; .... patternN actionsN end.
注意:在end之前没有“;”。
在Erlang进程之间传递的消息都是简单的合法的Erlang“短语(Term)”。可以是列表、元组、整数、常量或者pid什么的。
每个进程都有自己的输入消息队列,用以接受消息。新的消息到达该进程时被放在队列的末尾。当一个进程执行一个receive,队列中的第一个消息被receive中的第一个模式(pattern)匹配测试,如果匹配,该消息从消息队列中删除,并且执行对应pattern下的操作。
如此,如果第一个pattern没有被匹配成功,第二个模式就将被测试,如果匹配成功,该消息就会从消息队列中删除,并且执行对应pattern下的操作。如果第二个pattern仍然不能被测试为真,则该过程以此类推,直到没有pattern可供测试为止。如果没有pattern可供测试了,第一个消息将被保存在在消息队列中,并且开始第二个消息的匹配测试工作,如果这时第二个消息匹配成功了,则删除消息队列中的第二个消息,但是第一个消息和其他消息的状态并不受到任何影响。如果第二个消息还是匹配失败,则该过程持续下去,依次进行第三个、第四个消息的匹配。如果我们到了队列的末尾,该进程被阻塞(停止执行),并且等待新消息的到来,然后重新开始匹配的过程。
当然,Erlang的实现是非常“聪明”的,并且能够最小化每个消息被接收方的receive测试的次数。
现在回到我们的ping pong例子。
"Pong" 等待着消息。如果常量finished被接收到,“pong” 将输出“Pong finished”,然后继续“无所事事”。如果它接收一个消息是下面的格式:
{ping, Ping_PID}
它输出“Pong received ping”,并且发送常量pong到进程 “ping”:
Ping_PID ! pong
注意:操作符“!”怎样被用来发送消息的,下面是“!”的语法:
Pid ! Message
消息(可以是任何的Erlang的Term)被用来向Pid标识的的进程发送消息。
在pong发送消息到进程“ping”后,“pong”再次调用了 pong函数,这就让它回到了等待接受消息的那种状态,从而等待其他消息的到来。现在我们来看进程“ping”。回忆它是怎么开始运行的:
tut15:ping(3, Pong_PID)
看看ping/2,我们看到ping/2的第二个子句被执行,并且带有参数3(不是0)(第一个子句是ping(0,Pong_PID),第二个子句是 ping(N,Pong_PID),这里的N会被赋值为3)。
第二个子句发送到消息到“pong”:
Pong_PID ! {ping, self()},
self() 返回当前执行self()进程的pid,在这里就是“ping ”的pid。(回忆“pong”的代码,看看里面Pong_PID的情况)
"Ping"现在等待着从“pong”传回的信息:
receive
pong ->
io:format("Ping received pong~n", [])
end,
并且当回复的消息到达时会输出“Ping received pong”,之后“ping”会再次调用ping函数。
ping(N - 1, Pong_PID)
N-1 使得第一个参数递减,直到减到0为止。当减到0时,第一个子句ping/2会被执行:
ping(0, Pong_PID) ->
Pong_PID ! finished,
io:format("ping finished~n", []);
常量finished被发送给“pong”(这将导致接受方终止)并且输出“ping finished”。“ping”然后自己结束掉自己。