Erlang 消息傳遞

2022-07-07 15:41 更新

Erlang 消息傳遞

下面的例子中創(chuàng)建了兩個進程,它們相互之間會發(fā)送多個消息。

-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)建了一個進程,我們稱之為 “pong”:

Pong_PID = spawn(tut15, pong, [])

這個進程會執(zhí)行 tut15:pong 函數(shù)。Pong_PID 是 “pong” 進程的進程標識符。接下來,start 函數(shù)又創(chuàng)建了另外一個進程 ”ping“:

spawn(tut15,ping,[3,Pong_PID]),

這個進程執(zhí)行:

tut15:ping(3, Pong_PID)

<0.36.0> 為是 start 函數(shù)的返回值。

”pong“ 進程完成下面的工作:

receive
    finished ->
        io:format("Pong finished~n", []);
    {ping, Ping_PID} ->
        io:format("Pong received ping~n", []),
        Ping_PID ! pong,
        pong()
end.

receive 關(guān)鍵字被進程用來接收從其它進程發(fā)送的的消息。它的使用語法如下:

receive
   pattern1 ->
       actions1;
   pattern2 ->
       actions2;
   ....
   patternN
       actionsN
end.

請注意,在 end 前的最后一個 actions 并沒有 ";"。

Erlang 進程之間的消息可以是任何簡單的 Erlang 項。比如說,可以是列表、元組、整數(shù)、原子、進程標識等等。

每個進程都有獨立的消息接收隊列。新接收的消息被放置在接收隊列的尾部。當進程執(zhí)行 receive 時,消息中第一個消息與與 receive 后的第一個模塊進行匹配。如果匹配成功,則將該消息從消息隊列中刪除,并執(zhí)行該模式后面的代碼。

然而,如果第一個模式匹配失敗,則測試第二個匹配。如果第二個匹配成功,則將該消息從消息隊列中刪除,并執(zhí)行第二個匹配后的代碼。如果第二個匹配也失敗,則匹配第三個,依次類推,直到所有模式都匹配結(jié)束。如果所有匹配都失敗,則將第一個消息留在消息隊列中,使用第二個消息重復(fù)前面的過程。第二個消息匹配成功時,則執(zhí)行匹配成功后的程序并將消息從消息隊列中取出(將第一個消息與其余的消息繼續(xù)留在消息隊列中)。如果第二個消息也匹配失敗,則嘗試第三個消息,依次類推,直到嘗試完消息隊列所有的消息為止。如果所有消息都處理結(jié)束(匹配失敗或者匹配成功被移除),則進程阻塞,等待新的消息的到來。上面的過程將會一直重復(fù)下去。

Erlang 實現(xiàn)是非常 “聰明” 的,它會盡量減少 receive 的每個消息與模式匹配測試的次數(shù)。

讓我們回到 ping pong 示例程序。

“Pong” 一直等待接收消息。 如果收到原子值 finished,“Pong” 會輸出 “Pong finished”,然后結(jié)束進程。如果收到如下形式的消息:

{ping, Ping_PID}

則輸出 “Pong received ping”,并向進程 “ping” 發(fā)送一個原子值消息 pong:

Ping_PID ! pong

請注意這里是如何使用 “!” 操作符發(fā)送消息的。 “!” 操作符的語法如下所示:

Pid ! Message

這表示將消息(任何 Erlang 數(shù)據(jù))發(fā)送到進程標識符為 Pid 的進程的消息隊列中。

將消息 pong 發(fā)送給進程 “ping” 后,“pong” 進程再次調(diào)用 pong 函數(shù),這會使得再次回到 receive 等待下一個消息的到來。

下面,讓我們一起去看看進程 “ping”,回憶一下它是從下面的地方開始執(zhí)行的:

tut15:ping(3, Pong_PID)

可以看一下 ping/2 函數(shù),由于第一個參數(shù)的值是 3 而不是 0, 所以 ping/2 函數(shù)的第二個子句被執(zhí)行(第一個子句的頭為 ping(0,Pong_PID),第二個子句的頭部為 ping(N,Pong_PID),因此 N 為 3 。

第二個子句將發(fā)送消息給 “pong” 進程:

Pong_PID ! {ping, self()},

self() 函數(shù)返回當前進程(執(zhí)行 self() 的進程)的進程標識符,在這兒為 “ping” 進程的進程標識符。(回想一下 “pong” 的代碼,這個進程標識符值被存儲在變量 Ping_PID 當中)

發(fā)送完消息后,“Ping” 接下來等待回復(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 使得第一個參數(shù)逐漸減小到 0。當其值變?yōu)?0 后,ping/2 函數(shù)的第一個子句會被執(zhí)行。

ping(0, Pong_PID) ->
    Pong_PID !  finished,
    io:format("ping finished~n", []);

此時,原子值 finished 被發(fā)送至 “pong” 進程(會導(dǎo)致進程結(jié)束),同時將“ping finished” 輸出。隨后,“Ping” 進程結(jié)束。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號