下面的例子中創(chuàng)建了兩個(gè)進(jìn)程,它們相互之間會(huì)發(fā)送多個(gè)消息。
-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 函數(shù)先創(chuàng)建了一個(gè)進(jìn)程,我們稱之為 “pong”:
Pong_PID = spawn(tut15, pong, [])
這個(gè)進(jìn)程會(huì)執(zhí)行 tut15:pong 函數(shù)。Pong_PID 是 “pong” 進(jìn)程的進(jìn)程標(biāo)識(shí)符。接下來(lái),start 函數(shù)又創(chuàng)建了另外一個(gè)進(jìn)程 ”ping“:
spawn(tut15,ping,[3,Pong_PID]),
這個(gè)進(jìn)程執(zhí)行:
tut15:ping(3, Pong_PID)
<0.36.0>
為是 start 函數(shù)的返回值。
”pong“ 進(jìn)程完成下面的工作:
receive
finished ->
io:format("Pong finished~n", []);
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pong()
end.
receive 關(guān)鍵字被進(jìn)程用來(lái)接收從其它進(jìn)程發(fā)送的的消息。它的使用語(yǔ)法如下:
receive
pattern1 ->
actions1;
pattern2 ->
actions2;
....
patternN
actionsN
end.
請(qǐng)注意,在 end 前的最后一個(gè) actions 并沒有 ";"。
Erlang 進(jìn)程之間的消息可以是任何簡(jiǎn)單的 Erlang 項(xiàng)。比如說(shuō),可以是列表、元組、整數(shù)、原子、進(jìn)程標(biāo)識(shí)等等。
每個(gè)進(jìn)程都有獨(dú)立的消息接收隊(duì)列。新接收的消息被放置在接收隊(duì)列的尾部。當(dāng)進(jìn)程執(zhí)行 receive 時(shí),消息中第一個(gè)消息與與 receive 后的第一個(gè)模塊進(jìn)行匹配。如果匹配成功,則將該消息從消息隊(duì)列中刪除,并執(zhí)行該模式后面的代碼。
然而,如果第一個(gè)模式匹配失敗,則測(cè)試第二個(gè)匹配。如果第二個(gè)匹配成功,則將該消息從消息隊(duì)列中刪除,并執(zhí)行第二個(gè)匹配后的代碼。如果第二個(gè)匹配也失敗,則匹配第三個(gè),依次類推,直到所有模式都匹配結(jié)束。如果所有匹配都失敗,則將第一個(gè)消息留在消息隊(duì)列中,使用第二個(gè)消息重復(fù)前面的過(guò)程。第二個(gè)消息匹配成功時(shí),則執(zhí)行匹配成功后的程序并將消息從消息隊(duì)列中取出(將第一個(gè)消息與其余的消息繼續(xù)留在消息隊(duì)列中)。如果第二個(gè)消息也匹配失敗,則嘗試第三個(gè)消息,依次類推,直到嘗試完消息隊(duì)列所有的消息為止。如果所有消息都處理結(jié)束(匹配失敗或者匹配成功被移除),則進(jìn)程阻塞,等待新的消息的到來(lái)。上面的過(guò)程將會(huì)一直重復(fù)下去。
Erlang 實(shí)現(xiàn)是非常 “聰明” 的,它會(huì)盡量減少 receive 的每個(gè)消息與模式匹配測(cè)試的次數(shù)。
讓我們回到 ping pong 示例程序。
“Pong” 一直等待接收消息。 如果收到原子值 finished,“Pong” 會(huì)輸出 “Pong finished”,然后結(jié)束進(jìn)程。如果收到如下形式的消息:
{ping, Ping_PID}
則輸出 “Pong received ping”,并向進(jìn)程 “ping” 發(fā)送一個(gè)原子值消息 pong:
Ping_PID ! pong
請(qǐng)注意這里是如何使用 “!” 操作符發(fā)送消息的。 “!” 操作符的語(yǔ)法如下所示:
Pid ! Message
這表示將消息(任何 Erlang 數(shù)據(jù))發(fā)送到進(jìn)程標(biāo)識(shí)符為 Pid 的進(jìn)程的消息隊(duì)列中。
將消息 pong 發(fā)送給進(jìn)程 “ping” 后,“pong” 進(jìn)程再次調(diào)用 pong 函數(shù),這會(huì)使得再次回到 receive 等待下一個(gè)消息的到來(lái)。
下面,讓我們一起去看看進(jìn)程 “ping”,回憶一下它是從下面的地方開始執(zhí)行的:
tut15:ping(3, Pong_PID)
可以看一下 ping/2 函數(shù),由于第一個(gè)參數(shù)的值是 3 而不是 0, 所以 ping/2 函數(shù)的第二個(gè)子句被執(zhí)行(第一個(gè)子句的頭為 ping(0,Pong_PID)
,第二個(gè)子句的頭部為 ping(N,Pong_PID)
,因此 N 為 3 。
第二個(gè)子句將發(fā)送消息給 “pong” 進(jìn)程:
Pong_PID ! {ping, self()},
self() 函數(shù)返回當(dāng)前進(jìn)程(執(zhí)行 self() 的進(jìn)程)的進(jìn)程標(biāo)識(shí)符,在這兒為 “ping” 進(jìn)程的進(jìn)程標(biāo)識(shí)符。(回想一下 “pong” 的代碼,這個(gè)進(jìn)程標(biāo)識(shí)符值被存儲(chǔ)在變量 Ping_PID 當(dāng)中)
發(fā)送完消息后,“Ping” 接下來(lái)等待回復(fù)消息 “pong”:
receive
pong ->
io:format("Ping received pong~n", [])
end,
收到回復(fù)消息后,則輸出 “Ping received pong”。之后 “ping” 也再次調(diào)用 ping 函數(shù):
ping(N - 1, Pong_PID)
N-1 使得第一個(gè)參數(shù)逐漸減小到 0。當(dāng)其值變?yōu)?0 后,ping/2 函數(shù)的第一個(gè)子句會(huì)被執(zhí)行。
ping(0, Pong_PID) ->
Pong_PID ! finished,
io:format("ping finished~n", []);
此時(shí),原子值 finished 被發(fā)送至 “pong” 進(jìn)程(會(huì)導(dǎo)致進(jìn)程結(jié)束),同時(shí)將“ping finished” 輸出。隨后,“Ping” 進(jìn)程結(jié)束。
更多建議: