檢視原始碼 並行程式設計
程序
使用 Erlang 而不是其他函數式語言的主要原因之一,是 Erlang 處理並行和分散式程式設計的能力。並行指的是能夠同時處理多個執行緒的程式。例如,現代作業系統允許您同時執行文字處理器、試算表、郵件客戶端和列印作業。系統中的每個處理器 (CPU) 可能一次只處理一個執行緒(或作業),但它會在作業之間快速切換,讓您感覺它們都在同時執行。在 Erlang 程式中建立並行執行緒並允許這些執行緒相互通訊很容易。在 Erlang 中,每個執行緒都稱為程序。
(題外話:術語「程序」通常用於執行緒之間不共享任何資料的情況,而術語「執行緒」用於它們以某種方式共享資料的情況。Erlang 中的執行緒不共享資料,這就是它們被稱為程序的原因)。
Erlang BIF spawn
用於建立新的程序:spawn(模組, 導出的函式, 參數列表)
。請考慮以下模組
-module(tut14).
-export([start/0, say_something/2]).
say_something(What, 0) ->
done;
say_something(What, Times) ->
io:format("~p~n", [What]),
say_something(What, Times - 1).
start() ->
spawn(tut14, say_something, [hello, 3]),
spawn(tut14, say_something, [goodbye, 3]).
5> c(tut14).
{ok,tut14}
6> tut14:say_something(hello, 3).
hello
hello
hello
done
如所示,函式 say_something
會將其第一個參數寫入指定的次數(由第二個參數指定)。函式 start
會啟動兩個 Erlang 程序,一個寫入 "hello" 三次,另一個寫入 "goodbye" 三次。兩個程序都使用函式 say_something
。請注意,以這種方式由 spawn
用於啟動程序的函式,必須從模組中匯出(即,在模組開頭的 -export
中)。
9> tut14:start().
hello
goodbye
<0.63.0>
hello
goodbye
hello
goodbye
請注意,它不是先寫入 "hello" 三次,然後寫入 "goodbye" 三次。相反,第一個程序寫入一個 "hello",第二個程序寫入一個 "goodbye",第一個程序再寫入另一個 "hello",依此類推。但是 <0.63.0>
從哪裡來的?函式的傳回值是函式中最後一個「東西」的傳回值。函式 start
中的最後一個東西是
spawn(tut14, say_something, [goodbye, 3]).
spawn
會傳回一個程序識別碼或 pid,它會唯一識別程序。因此,<0.63.0>
是上面 spawn
函式呼叫的 pid。下一個範例將示範如何使用 pid。
另請注意,在 io:format/2
中使用的是 ~p 而不是 ~w。引用 手冊
~p 以與 ~w 相同的方式使用標準語法寫入資料,但會將列印表示形式超過一行的術語分成多行,並合理地縮排每一行。它也會嘗試偵測可列印字元的平面清單,並將這些輸出為字串
訊息傳遞
在以下範例中,會建立兩個程序,並且它們會相互傳送訊息多次。
-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 術語。也就是說,它們可以是清單、元組、整數、原子、pid 等。
每個程序都有自己的輸入佇列,用於接收訊息。接收到的新訊息會放在佇列的末尾。當程序執行 receive
時,佇列中的第一個訊息會與 receive
中的第一個模式進行比對。如果比對成功,則會從佇列中移除訊息,並執行對應於該模式的動作。
但是,如果第一個模式不符,則會測試第二個模式。如果比對成功,則會從佇列中移除訊息,並執行對應於第二個模式的動作。如果第二個模式不符,則會嘗試第三個模式,依此類推,直到沒有更多模式可以測試。如果沒有更多模式可以測試,則會將第一個訊息保留在佇列中,並改為嘗試第二個訊息。如果第二個訊息與任何模式比對成功,則會執行適當的動作,並從佇列中移除第二個訊息(保留第一個訊息和佇列中的任何其他訊息)。如果第二個訊息不符,則會嘗試第三個訊息,依此類推,直到到達佇列的末尾。如果到達佇列的末尾,則程序會封鎖(停止執行),並等待接收到新訊息,然後重複此程序。
Erlang 實作很「聰明」,會盡量減少針對每個 receive
中的模式測試每個訊息的次數。
現在回到 ping pong 範例。
「Pong」正在等待訊息。如果收到原子 finished
,「pong」會將 "Pong finished" 寫入輸出,並且由於它沒有其他事情可做,因此會終止。如果收到格式為
{ping, Ping_PID}
的訊息,它會將 "Pong received ping" 寫入輸出,並將原子 pong
傳送至程序「ping」
Ping_PID ! pong
請注意如何使用運算子 "!" 來傳送訊息。"!" 的語法是
Pid ! Message
也就是說,Message
(任何 Erlang 術語)會傳送至識別碼為 Pid
的程序。
在將訊息 pong
傳送至程序「ping」之後,「pong」會再次呼叫 pong
函式,這會使其回到 receive
,並等待另一個訊息。
現在讓我們看看程序「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/0
會傳回執行 self/0
的程序的 pid,在此情況下為「ping」的 pid。(回想一下「pong」的程式碼,這會在先前說明的 receive
中的變數 Ping_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 為止。當這種情況發生時,會執行 ping/2
的第一個子句
ping(0, Pong_PID) ->
Pong_PID ! finished,
io:format("ping finished~n", []);
原子 finished
會傳送至「pong」(使其如上所述終止),並且 "ping finished" 會寫入輸出。「Ping」隨後終止,因為它沒有其他事情可做。
已註冊的程序名稱
在上面的範例中,首先建立「pong」,以便在啟動「ping」時能夠提供「pong」的識別碼。也就是說,「ping」必須以某種方式知道「pong」的識別碼才能傳送訊息給它。有時需要知道彼此識別碼的程序是獨立啟動的。因此,Erlang 提供了一種機制,讓程序可以被賦予名稱,以便這些名稱可以用作識別碼,而不是 pid。這是透過使用 register
BIF 來完成的
register(some_atom, Pid)
現在,讓我們使用它來重寫 ping pong 範例,並將名稱 pong
賦予「pong」程序
-module(tut16).
-export([start/0, ping/1, pong/0]).
ping(0) ->
pong ! finished,
io:format("ping finished~n", []);
ping(N) ->
pong ! {ping, self()},
receive
pong ->
io:format("Ping received pong~n", [])
end,
ping(N - 1).
pong() ->
receive
finished ->
io:format("Pong finished~n", []);
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pong()
end.
start() ->
register(pong, spawn(tut16, pong, [])),
spawn(tut16, ping, [3]).
2> c(tut16).
{ok, tut16}
3> tut16:start().
<0.38.0>
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
ping finished
Pong finished
這裡的 start/0
函式
register(pong, spawn(tut16, pong, [])),
會產生「pong」程序,並將名稱 pong
賦予它。在「ping」程序中,訊息可以透過以下方式傳送至 pong
pong ! {ping, self()},
ping/2
現在變成 ping/1
,因為不需要引數 Pong_PID
。
分散式程式設計
讓我們在不同的電腦上重寫「ping」和「pong」的分散式 ping pong 程式。首先,需要一些東西來設定才能讓它運作。分散式 Erlang 實作提供非常基本的驗證機制,以防止意外存取另一台電腦上的 Erlang 系統。相互通訊的 Erlang 系統必須具有相同的魔術 cookie。最簡單的方法是在您要執行彼此通訊的 Erlang 系統的所有機器上的主目錄中,建立一個名為 .erlang.cookie
的檔案
- 在 Windows 系統上,主目錄是由環境變數 $HOME 指出的目錄,您可能需要設定此變數。
- 在 Linux 或 UNIX 上,您可以安全地忽略此變數,並且只需在執行不帶任何引數的指令
cd
後所進入的目錄中,建立一個名為.erlang.cookie
的檔案即可。
.erlang.cookie
檔案應該包含一行與節點名稱相同的原子(atom)。例如,在 Linux 或 UNIX 的作業系統 shell 中:
$ cd
$ cat > .erlang.cookie
this_is_very_secret
$ chmod 400 .erlang.cookie
上面的 chmod
命令讓 .erlang.cookie
檔案只能被檔案擁有者存取,這是必要的要求。
當你啟動一個要與其他 Erlang 系統通訊的 Erlang 系統時,你必須給它一個名稱,例如:
$ erl -sname my_name
我們稍後會看到更多細節。如果你想實驗分散式 Erlang,但只有一台電腦可以使用,你可以在同一台電腦上啟動兩個獨立的 Erlang 系統,但給它們不同的名稱。在電腦上運行的每個 Erlang 系統都稱為一個 *Erlang 節點*。
(注意:erl -sname
假設所有節點都在同一個 IP 網域中,我們只能使用 IP 位址的第一個部分。如果想在不同的網域中使用節點,我們改用 -name
,但這樣所有的 IP 位址都必須完整給出。)
這裡是修改過可在兩個獨立節點上運行的 ping pong 範例:
-module(tut17).
-export([start_ping/1, start_pong/0, ping/2, pong/0]).
ping(0, Pong_Node) ->
{pong, Pong_Node} ! finished,
io:format("ping finished~n", []);
ping(N, Pong_Node) ->
{pong, Pong_Node} ! {ping, self()},
receive
pong ->
io:format("Ping received pong~n", [])
end,
ping(N - 1, Pong_Node).
pong() ->
receive
finished ->
io:format("Pong finished~n", []);
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pong()
end.
start_pong() ->
register(pong, spawn(tut17, pong, [])).
start_ping(Pong_Node) ->
spawn(tut17, ping, [3, Pong_Node]).
假設有兩台電腦,分別叫做 gollum 和 kosken。首先在 kosken 上啟動一個名為 ping 的節點,然後在 gollum 上啟動一個名為 pong 的節點。
在 kosken 上(Linux/UNIX 系統):
kosken> erl -sname ping
Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
Eshell V5.2.3.7 (abort with ^G)
(ping@kosken)1>
在 gollum 上:
gollum> erl -sname pong
Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
Eshell V5.2.3.7 (abort with ^G)
(pong@gollum)1>
現在 gollum 上的 "pong" 處理程序已啟動:
(pong@gollum)1> tut17:start_pong().
true
kosken 上的 "ping" 處理程序也啟動了(從上面的程式碼可以看出,start_ping
函數的一個參數是執行 "pong" 的 Erlang 系統的節點名稱):
(ping@kosken)1> tut17:start_ping(pong@gollum).
<0.37.0>
Ping received pong
Ping received pong
Ping received pong
ping finished
如所示,ping pong 程式已經執行。在 "pong" 端:
(pong@gollum)2>
Pong received ping
Pong received ping
Pong received ping
Pong finished
(pong@gollum)2>
查看 tut17
程式碼,你會發現 pong
函數本身沒有改變,以下幾行程式碼的運作方式相同,無論 "ping" 處理程序在哪個節點執行:
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
因此,Erlang 的 pid 包含有關處理程序執行位置的資訊。因此,如果你知道處理程序的 pid,可以使用 !
運算符向它發送訊息,無論該處理程序是在同一個節點還是在不同的節點上。
一個不同之處是如何向另一個節點上已註冊的處理程序發送訊息:
{pong, Pong_Node} ! {ping, self()},
使用一個 tuple {registered_name,node_name}
來代替單純的 registered_name
。
在先前的範例中,"ping" 和 "pong" 是從兩個獨立 Erlang 節點的 shell 中啟動的。spawn
也可以用來在其他節點中啟動處理程序。
下一個範例是 ping pong 程式,再次呈現,但這次 "ping" 是在另一個節點中啟動:
-module(tut18).
-export([start/1, ping/2, pong/0]).
ping(0, Pong_Node) ->
{pong, Pong_Node} ! finished,
io:format("ping finished~n", []);
ping(N, Pong_Node) ->
{pong, Pong_Node} ! {ping, self()},
receive
pong ->
io:format("Ping received pong~n", [])
end,
ping(N - 1, Pong_Node).
pong() ->
receive
finished ->
io:format("Pong finished~n", []);
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pong()
end.
start(Ping_Node) ->
register(pong, spawn(tut18, pong, [])),
spawn(Ping_Node, tut18, ping, [3, node()]).
假設在 kosken 上已經啟動了一個名為 ping 的 Erlang 系統(但不是 "ping" 處理程序),那麼在 gollum 上執行以下操作:
(pong@gollum)1> tut18:start(ping@kosken).
<3934.39.0>
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong finished
ping finished
請注意,所有輸出都接收在 gollum 上。這是因為 I/O 系統會找出處理程序的產生位置,並將所有輸出發送到那裡。
一個更大的範例
現在來看一個更大的範例,使用一個簡單的「訊息傳遞器」(messenger)。訊息傳遞器是一個程式,允許使用者在不同的節點上登入,並互相發送簡單的訊息。
在開始之前,請注意以下事項:
- 此範例僅顯示訊息傳遞邏輯 - 沒有嘗試提供良好的圖形使用者介面,儘管這也可以在 Erlang 中完成。
- 這類問題可以使用 OTP 中的功能更容易地解決,OTP 也提供了動態更新程式碼的方法等等(請參閱OTP 設計原則)。
- 第一個程式在處理消失的節點方面存在一些不足。這些問題會在稍後的程式版本中修正。
訊息傳遞器的設定方式是允許「客戶端」連接到中央伺服器,並說明他們是誰以及他們的位置。也就是說,使用者不需要知道另一個使用者所在的 Erlang 節點名稱就可以發送訊息。
檔案 messenger.erl
:
%%% Message passing utility.
%%% User interface:
%%% logon(Name)
%%% One user at a time can log in from each Erlang node in the
%%% system messenger: and choose a suitable Name. If the Name
%%% is already logged in at another node or if someone else is
%%% already logged in at the same node, login will be rejected
%%% with a suitable error message.
%%% logoff()
%%% Logs off anybody at that node
%%% message(ToName, Message)
%%% sends Message to ToName. Error messages if the user of this
%%% function is not logged on or if ToName is not logged on at
%%% any node.
%%%
%%% One node in the network of Erlang nodes runs a server which maintains
%%% data about the logged on users. The server is registered as "messenger"
%%% Each node where there is a user logged on runs a client process registered
%%% as "mess_client"
%%%
%%% Protocol between the client processes and the server
%%% ----------------------------------------------------
%%%
%%% To server: {ClientPid, logon, UserName}
%%% Reply {messenger, stop, user_exists_at_other_node} stops the client
%%% Reply {messenger, logged_on} logon was successful
%%%
%%% To server: {ClientPid, logoff}
%%% Reply: {messenger, logged_off}
%%%
%%% To server: {ClientPid, logoff}
%%% Reply: no reply
%%%
%%% To server: {ClientPid, message_to, ToName, Message} send a message
%%% Reply: {messenger, stop, you_are_not_logged_on} stops the client
%%% Reply: {messenger, receiver_not_found} no user with this name logged on
%%% Reply: {messenger, sent} Message has been sent (but no guarantee)
%%%
%%% To client: {message_from, Name, Message},
%%%
%%% Protocol between the "commands" and the client
%%% ----------------------------------------------
%%%
%%% Started: messenger:client(Server_Node, Name)
%%% To client: logoff
%%% To client: {message_to, ToName, Message}
%%%
%%% Configuration: change the server_node() function to return the
%%% name of the node where the messenger server runs
-module(messenger).
-export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).
%%% Change the function below to return the name of the node where the
%%% messenger server runs
server_node() ->
messenger@super.
%%% This is the server process for the "messenger"
%%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
server(User_List) ->
receive
{From, logon, Name} ->
New_User_List = server_logon(From, Name, User_List),
server(New_User_List);
{From, logoff} ->
New_User_List = server_logoff(From, User_List),
server(New_User_List);
{From, message_to, To, Message} ->
server_transfer(From, To, Message, User_List),
io:format("list is now: ~p~n", [User_List]),
server(User_List)
end.
%%% Start the server
start_server() ->
register(messenger, spawn(messenger, server, [[]])).
%%% Server adds a new user to the user list
server_logon(From, Name, User_List) ->
%% check if logged on anywhere else
case lists:keymember(Name, 2, User_List) of
true ->
From ! {messenger, stop, user_exists_at_other_node}, %reject logon
User_List;
false ->
From ! {messenger, logged_on},
[{From, Name} | User_List] %add user to the list
end.
%%% Server deletes a user from the user list
server_logoff(From, User_List) ->
lists:keydelete(From, 1, User_List).
%%% Server transfers a message between user
server_transfer(From, To, Message, User_List) ->
%% check that the user is logged on and who he is
case lists:keysearch(From, 1, User_List) of
false ->
From ! {messenger, stop, you_are_not_logged_on};
{value, {From, Name}} ->
server_transfer(From, Name, To, Message, User_List)
end.
%%% If the user exists, send the message
server_transfer(From, Name, To, Message, User_List) ->
%% Find the receiver and send the message
case lists:keysearch(To, 2, User_List) of
false ->
From ! {messenger, receiver_not_found};
{value, {ToPid, To}} ->
ToPid ! {message_from, Name, Message},
From ! {messenger, sent}
end.
%%% User Commands
logon(Name) ->
case whereis(mess_client) of
undefined ->
register(mess_client,
spawn(messenger, client, [server_node(), Name]));
_ -> already_logged_on
end.
logoff() ->
mess_client ! logoff.
message(ToName, Message) ->
case whereis(mess_client) of % Test if the client is running
undefined ->
not_logged_on;
_ -> mess_client ! {message_to, ToName, Message},
ok
end.
%%% The client process which runs on each server node
client(Server_Node, Name) ->
{messenger, Server_Node} ! {self(), logon, Name},
await_result(),
client(Server_Node).
client(Server_Node) ->
receive
logoff ->
{messenger, Server_Node} ! {self(), logoff},
exit(normal);
{message_to, ToName, Message} ->
{messenger, Server_Node} ! {self(), message_to, ToName, Message},
await_result();
{message_from, FromName, Message} ->
io:format("Message from ~p: ~p~n", [FromName, Message])
end,
client(Server_Node).
%%% wait for a response from the server
await_result() ->
receive
{messenger, stop, Why} -> % Stop the client
io:format("~p~n", [Why]),
exit(normal);
{messenger, What} -> % Normal response
io:format("~p~n", [What])
end.
要使用此程式,你需要:
- 配置
server_node()
函數。 - 將編譯後的程式碼(
messenger.beam
)複製到每個啟動 Erlang 的電腦上的目錄中。
在以下使用此程式的範例中,節點會在四台不同的電腦上啟動。如果你的網路上沒有那麼多機器可用,你可以在同一台機器上啟動多個節點。
啟動了四個 Erlang 節點:messenger@super、c1@bilbo、c2@kosken、c3@gollum。
首先啟動 messenger@super 上的伺服器:
(messenger@super)1> messenger:start_server().
true
現在 Peter 在 c1@bilbo 上登入:
(c1@bilbo)1> messenger:logon(peter).
true
logged_on
James 在 c2@kosken 上登入:
(c2@kosken)1> messenger:logon(james).
true
logged_on
Fred 在 c3@gollum 上登入:
(c3@gollum)1> messenger:logon(fred).
true
logged_on
現在 Peter 發送訊息給 Fred:
(c1@bilbo)2> messenger:message(fred, "hello").
ok
sent
Fred 接收到訊息,並發送訊息給 Peter,然後登出:
Message from peter: "hello"
(c3@gollum)2> messenger:message(peter, "go away, I'm busy").
ok
sent
(c3@gollum)3> messenger:logoff().
logoff
現在 James 嘗試發送訊息給 Fred:
(c2@kosken)2> messenger:message(fred, "peter doesn't like you").
ok
receiver_not_found
但這會失敗,因為 Fred 已經登出。
首先,讓我們看看一些新引入的概念。
server_transfer
函數有兩個版本:一個有四個參數 (server_transfer/4
),另一個有五個參數 (server_transfer/5
)。Erlang 將它們視為兩個獨立的函數。
請注意如何編寫 server
函數,使其透過 server(User_List)
呼叫自身,從而建立一個迴圈。Erlang 編譯器很「聰明」,會優化程式碼,使其真正成為一種迴圈,而不是一個正確的函數呼叫。但是,這僅在呼叫後沒有程式碼的情況下才有效。否則,編譯器會期望該呼叫返回並進行正確的函數呼叫。這會導致處理程序在每次迴圈時變得越來越大。
使用了 lists
模組中的函數。這是一個非常有用的模組,建議研究其說明文件 (erl -man lists
)。lists:keymember(Key,Position,Lists)
會檢查 tuple 的 list,並查看每個 tuple 中的 Position
是否與 Key
相同。第一個元素的位置是 1。如果找到一個 tuple,其中 Position
的元素與 Key
相同,它會返回 true
,否則返回 false
。
3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
true
4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
false
lists:keydelete
的工作方式相同,但會刪除找到的第一個 tuple(如果有的話),並返回剩餘的 list。
5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
[{x,y,z},{b,b,b},{q,r,s}]
lists:keysearch
與 lists:keymember
類似,但它返回 {value,Tuple_Found}
或 atom false
。
lists
模組中有很多非常有用的函數。
一個 Erlang 處理程序(概念上)會持續運行,直到它執行 receive
,並且訊息佇列中沒有它想要接收的訊息。這裡使用「概念上」是因為 Erlang 系統會在系統中的活動處理程序之間共享 CPU 時間。
當處理程序沒有其他事情要做時,它就會終止。也就是說,它呼叫的最後一個函數只是返回,而不會呼叫另一個函數。處理程序終止的另一種方式是呼叫exit/1
。 exit/1
的參數具有特殊的意義,稍後會討論。在此範例中,執行 exit(normal)
,其效果與處理程序用完要呼叫的函數相同。
BIF whereis(RegisteredName)
會檢查是否存在名為 RegisteredName
的已註冊處理程序。如果存在,則會返回該處理程序的 pid。如果不存在,則返回 atom undefined
。
現在你應該能夠理解訊息傳遞器模組中的大部分程式碼。讓我們詳細研究一個案例:一個使用者向另一個使用者發送訊息。
在上面的範例中,第一個使用者透過以下方式「發送」訊息:
messenger:message(fred, "hello")
在測試客戶端處理程序是否存在之後:
whereis(mess_client)
訊息會發送到 mess_client
:
mess_client ! {message_to, fred, "hello"}
客戶端透過以下方式將訊息發送到伺服器:
{messenger, messenger@super} ! {self(), message_to, fred, "hello"},
並等待伺服器的回覆。
伺服器接收到此訊息並呼叫:
server_transfer(From, fred, "hello", User_List),
這會檢查 pid From
是否在 User_List
中:
lists:keysearch(From, 1, User_List)
如果 keysearch
返回 atom false
,則表示發生了一些錯誤,並且伺服器會發回訊息:
From ! {messenger, stop, you_are_not_logged_on}
客戶端會接收到此訊息,進而執行 exit(normal)
並終止。如果 keysearch
返回 {value,{From,Name}}
,則可以確定使用者已登入,且他的名稱 (peter) 在變數 Name
中。
現在讓我們呼叫:
server_transfer(From, peter, fred, "hello", User_List)
請注意,因為這是 server_transfer/5
,所以它與先前的函數 server_transfer/4
不同。另一個 keysearch
會在 User_List
上執行,以找到對應於 fred 的客戶端 pid:
lists:keysearch(fred, 2, User_List)
這次使用參數 2,這是 tuple 中的第二個元素。如果這返回 atom false
,則表示 fred 沒有登入,並且會發送以下訊息:
From ! {messenger, receiver_not_found};
客戶端會接收到此訊息。
如果 keysearch
返回:
{value, {ToPid, fred}}
則會將以下訊息發送到 fred 的客戶端:
ToPid ! {message_from, peter, "hello"},
會將以下訊息發送到 peter 的客戶端:
From ! {messenger, sent}
Fred 的客戶端接收到訊息並列印出來:
{message_from, peter, "hello"} ->
io:format("Message from ~p: ~p~n", [peter, "hello"])
Peter 的客戶端在 await_result
函數中接收到訊息。