【问题标题】:Concurrency in Erlang and its proper workflowErlang 中的并发性及其正确的工作流程
【发布时间】:2013-10-05 05:43:49
【问题描述】:

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

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

-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).

【问题讨论】:

    标签: concurrency erlang


    【解决方案1】:

    让我们看看前两行。

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

    第一个是模块声明,它的参数是一个atom(或者,换句话说,一个小写单词,不带引号)。摘自Learn You Some Erlang

    -module(Name).
    这始终是文件的第一个属性(和语句),并且有充分的理由:它是当前模块的名称,其中 Name 是一个原子。这是您将用于从其他模块调用函数的名称。调用以 M:F(A) 形式进行,其中 M 是模块名称,F 是函数,而 A 论据。

    第二句告诉你的编译器让所有声明的函数publicie,你写在那个模块上的每个函数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 标识的进程——这就是! 运算符的用途。最后,函数调用自身,以便再次查看邮箱。


    接下来您将在控制台(可能在另一个节点/机器上)编写的内容将是对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 还有更多要定义的函数)。

    你用3 作为第一个参数来调用它。由于30 不匹配,因此进程执行第二种情况,并以N 作为其参数。

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

    N 达到零时,执行第一个case,将finished 发送到{pong, Pong_Node},并结束执行。


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

    【讨论】:

    • 不错的答案需要一些时间才能写下来,我敢肯定!
    猜你喜欢
    • 2011-06-27
    • 2011-05-24
    • 2015-07-12
    • 2021-05-27
    • 1970-01-01
    • 1970-01-01
    • 2011-01-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多