Erlang中的并发性及其正确的工作流程

时间:2013-09-29 08:28:58

标签: concurrency erlang

这段代码是由我们的老师给我们的,但很遗憾,没有解释。我们刚刚在课堂上试过这个,然后被解雇了。

如果有人能够彻底向我解释这段代码,那将会非常有帮助。提前谢谢。

-module(pingpong).
-compile(export_all).

start_pong() ->
    register(pong, spawn(pingpong,pong,[])).

pong() ->
    receive
        finished ->
            io:format("Pong finished ~n");
        {ping, Ping_Pid} ->
            io:format("i am the receiver ~n"),
        Ping_Pid ! pong,
        pong()
end.

start_ping(Pong_Node) ->
    spawn(pingpong, ping, [3, Pong_Node]).

ping(0, Pong_Node) ->
    {pong, Pong_Node} ! finished,
    io:format("Pong finished ~n");

ping(N, Pong_Node) ->
    {pong, Pong_Node} ! {ping, self()},
    receive
        pong ->
            io:format("i am the sender ~n")
    end,
    ping(N-1,Pong_Node).

1 个答案:

答案 0 :(得分:8)

让我们来看看前两行。

-module(pingpong).
-compile(export_all).

第一个是模块声明,其参数是 atom (换句话说,是一个小写的单词,没有引号)。取自了解你一些Erlang

  

<强> -module(Name).
  这始终是文件的第一个属性(和语句),并且有充分的理由:它是当前模块的名称,其中Name是一个原子。这是您用来从其他模块调用函数的名称。调用是使用 M:F(A) 表单进行的,其中 M 是模块名称, F 函数和 A 参数。

第二句告诉你的编译器使所有声明的函数 public ,你在该模块上编写的每个函数F都能被调用由外人pingpong:F 这可能会简化您第一次学习时的过程,但这通常是一种不好的做法。请参阅this question


现在让我们来看看这些功能。

start_pong() ->
    register(pong, spawn(pingpong,pong,[])).

这可能是您的代码开始的地方。编译模块,然后在给定计算机或节点的Erlang shell中调用pingpong:start_pong().。所有这个功能都是“将名称 pong 注册为我即将创建的进程的标识符,spawn

因此,spawn创建了一个Erlang进程。 spawn也是内置函数(BIF),因此不需要在其前面添加模块名称。它的论点是spawn(Module, Exported_Function, List of Arguments),见in the documentation 回顾start_pong,它所做的只是“创建一个进程,该进程将在此模块中运行pong函数开始,没有参数,并调用该进程 pong


pong() ->
    receive
        finished ->
            io:format("Pong finished ~n");
        {ping, Ping_Pid} ->
            io:format("i am the receiver ~n"),
        Ping_Pid ! pong,
        pong()
end.

start_pong中新创建的流程将运行此功能。 Erlang中的每个进程都有自己的邮箱。进程通过在这些邮箱中保留邮件来相互通信。消息可能几乎任何内容。将它们视为您希望在进程之间发送的一些数据。

新进程进入receive语句,该语句告诉它从其邮箱中获取邮件,或者等到有一些邮件。然后,当收到消息时,它使用模式匹配来查找相应的操作。如果您习惯于命令式语言,请将其视为switch,否则请忽略此语句。

如果进程有一条带有单个原子finished的消息,它会在控制台中输出Pong finished并退出。
如果进程的消息是与原子ping进程标识符 pid - 每个进程都有一个)的对,那么它将执行剩余的函数代码。

大写Ping_Pid告诉Erlang将消息的第二个值分配给名为Ping_Pid的变量。碰巧你期待一个 pid 在输入这种情况时,它的作用是打印i am the receiver,然后将带有原子pong的消息发送到由Ping_Pid标识的进程 - 这就是!运算符是什么对于。 Finnaly,该函数调用自身,以便再次查看邮箱。


您在控制台上编写的下一件事,可能是在另一个节点/机器上,将调用start_ping

start_ping(Pong_Node) ->
    spawn(pingpong, ping, [3, Pong_Node]).

正如我们之前看到的,所有这一切都是创建一个运行ping函数的进程,其中包含参数3和它接收的Pong_Node,即机器(节点)第一个进程正在运行。


ping(0, Pong_Node) ->
    {pong, Pong_Node} ! finished,
    io:format("Pong finished ~n");

ping(N, Pong_Node) ->
    {pong, Pong_Node} ! {ping, self()},
    receive
        pong ->
            io:format("i am the sender ~n")
    end,
    ping(N-1,Pong_Node).

此函数在两种情况下定义(请注意,第一个ping块以;结束,而不是. - 这告诉Erlang还有更多定义函数的内容。 / p>

您可以使用3作为第一个参数调用它。由于30不匹配,因此该过程执行第二种情况,其中N为其参数。

此过程将{ping, self()}对发送到{pong, Pong_Node}给出的进程,该进程遵循语法{registered_name, node_name}self()用于检索当前进程自己的 pid 在此之后,该过程等待pong响应,并再次重复此过程,而N大于零。

N达到零时,执行第一种情况,将finished发送到{pong, Pong_Node},然后结束执行。


如果你觉得这个解释不完整,你也可以看看at the tutorial,它描绘了这个确切的程序。