查看源码 并发编程

进程

使用 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

也就是说,将 消息(任何 Erlang 术语)发送到具有标识 Pid 的进程。

在将消息 pong 发送到进程 "ping" 后,"pong" 再次调用 pong 函数,这会使其返回到 receive 并等待另一条消息。

现在让我们看看进程 "ping"。回想一下,它是通过执行启动的

tut15:ping(3, Pong_PID)

查看函数 ping/2,由于第一个参数的值为 3(而不是 0),因此执行 ping/2 的第二个子句(第一个子句头为 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 文件应包含一行具有相同原子的行。例如,在 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()},

使用元组 {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()]).

假设一个名为 ping 的 Erlang 系统(但不是“ping”进程)已经在 kosken 上启动,然后在 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) 遍历一个元组列表,并查看每个元组中的 Position 以查看它是否与 Key 相同。第一个元素是位置 1。如果它找到一个元组,其中 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 以相同的方式工作,但会删除找到的第一个元组(如果有),并返回剩余的列表

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} 或原子 false

lists 模块中有许多非常有用的函数。

Erlang 进程(在概念上)会一直运行,直到它执行 receive 并且消息队列中没有它想要接收的消息。这里使用“概念上”是因为 Erlang 系统在系统中的活动进程之间共享 CPU 时间。

当进程没有任何事情可做时,即它调用的最后一个函数只是返回并且不调用另一个函数时,进程就会终止。进程终止的另一种方式是调用 exit/1exit/1 的参数具有特殊含义,将在后面讨论。在此示例中,执行了 exit(normal),这与进程运行完所有要调用的函数具有相同的效果。

BIF whereis(RegisteredName) 检查是否存在名为 RegisteredName 的已注册进程。如果存在,则返回该进程的 pid。如果不存在,则返回原子 undefined

您现在应该能够理解 messenger 模块中的大部分代码。让我们详细研究一种情况:消息从一个用户发送到另一个用户。

在上面的示例中,第一个用户通过以下方式“发送”消息

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 返回原子 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 不同。在 User_List 上执行另一个 keysearch,以查找与 fred 对应的客户端的 pid

lists:keysearch(fred, 2, User_List)

这次使用参数 2,它是元组中的第二个元素。如果这返回原子 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 函数中收到消息。