简体   繁体   English

等待进程Erlang的回复

[英]Wait on the reply of a process Erlang

Is it possible to spawn a process p in a function funct1 of a module module1, to send a message to p in a function funct2 of module1 and to wait for a reply of p inside funct2, without having to spawn f2 that is therefore considered as self()? 是否有可能在模块module1的功能funct1中生成进程p,在模块1的功能funct2中向p发送消息,并在funct2内部等待p的答复,而不必生成因此被认为是f2的f2。自()? If so, what is the best way to implement the waiting part? 如果是这样,实现等待部分的最佳方法是什么? You can see the code below to have an overview of what I am looking for. 您可以查看下面的代码以大致了解我要查找的内容。 Thanks in advance. 提前致谢。

-module(module1)
...
funct1(...)
->  

 Pid = spawn(module2, function3, [[my_data]]),
 ...


funct2(...)
->
  ...
  Pid ! {self(), {data1, data2}},

  % wait here for the reply from Pid
  % do something here based on the reply.

The Answer 答案

Yes. 是。

The Real Problem 真正的问题

You are conflating three concepts: 您正在混淆三个概念:

  • Process (who is self() and what is pid() ) 进程(谁是self()和什么是pid()
  • Function 功能
  • Module 模组

A process is a living thing. 一个过程是有生命的东西。 A process has its own memory space. 进程具有其自己的内存空间。 These processes are the things making calls . 这些过程就是打电话的过程 This is the only identity that really matters. 这是唯一真正重要的身份。 When you think about "who is self() in this case" you are really asking "what is the calling context?" 当您想到“在这种情况下谁是self() ”时,您实际上是在问“调用上下文是什么?” If I spawn two instances of a process, they both might call the same function at some point in their lives -- but the context of those calls are completely different because the processes have their own lives and their own memory spaces. 如果我生成一个进程的两个实例,它们可能会在其生命中的某个时刻调用相同的函数-但这些调用的上下文是完全不同的,因为进程具有自己的生命和自己的内存空间。 Just because Victor and Victoria are both jumping rope at the same time doesn't make them the same person. 仅仅因为Victor和Victoria都在同一时间跳绳,并不会使他们成为同一个人。

Where people get mixed up about calling context the most is when writing module interface functions . 人们对调用上下文最困惑的地方是编写模块接口函数时 Most modules are, for the sake of simplicity, written in a way that they define just a single process. 为了简单起见,大多数模块都是以仅定义一个过程的方式编写的。 There is no rule that mandates this, but it is pretty easy to understand what a module does when it is written this way. 没有强制要求这样做的规则,但是以这种方式编写模块时,很容易理解模块的功能。 Interface functions are exported and available for any process to call -- and they are calling in the context of the processes calling them, not in the context of a process spawned to "be an instance of that module" and run the service loop defined therein. 接口函数已导出,可供任何进程调用-它们在调用它们的进程的上下文中进行调用, 而不是在生成为“成为该模块的实例”并运行其中定义的服务循环的进程的上下文中进行调用。

There is nothing trapping a process "within" that module, though. 但是,没有什么东西可以进程在该模块中。 I could write a pair of modules, one that defines the AI of a lion and another that defines the AI of a shark, and have a process essentially switch identities in the middle of its execution -- but this is almost always a really bad idea (because it gets confusing). 我可以编写一对模块,一个模块定义狮子的AI,另一个模块定义鲨鱼的AI,并在其执行过程中使进程本质上切换身份-但这总是一个非常糟糕的主意(因为它变得令人困惑)。

Functions are just functions. 功能就是功能。 That's all they are. 仅此而已。 Modules are composed of functions. 模块由功能组成。 There is nothing more to say than this. 没什么可说的了。

How to wait for a message 如何等待消息

We wait for messages using the receive construct. 我们使用receive构造等待消息。 It matches on the received message (which will always be an Erlang term) and selects what to do based on the shape and/or content of the message. 它与收到的消息匹配(将始终是Erlang术语),并根据消息的形状和/或内容选择要执行的操作。

Read the following very carefully : 仔细阅读以下内容:

1> Talker =
1>   fun T() ->
1>     receive
1>       {tell, Pid, Message} ->
1>         ok = io:format("~p: sending ~p message ~p~n", [self(), Pid, Message]),
1>         Pid ! {message, Message, self()},
1>         T();
1>       {message, Message, From} ->
1>         ok = io:format("~p: from ~p received message ~p~n", [self(), From, Message]),
1>         T();
1>       exit ->
1>         exit(normal)
1>     end
1>   end.
#Fun<erl_eval.44.87737649>
2> {Pid1, Ref1} = spawn_monitor(Talker).
{<0.64.0>,#Ref<0.1042362935.2208301058.9128>}
3> {Pid2, Ref2} = spawn_monitor(Talker).
{<0.69.0>,#Ref<0.1042362935.2208301058.9139>}
4> Pid1 ! {tell, Pid2, "A CAPITALIZED MESSAGE! RAAAR!"}.
<0.64.0>: sending <0.69.0> message "A CAPITALIZED MESSAGE! RAAAR!"
{tell,<0.69.0>,"A CAPITALIZED MESSAGE! RAAAR!"}
<0.69.0>: from <0.64.0> received message "A CAPITALIZED MESSAGE! RAAAR!"
5> Pid2 ! {tell, Pid1, "a lower cased message..."}.
<0.69.0>: sending <0.64.0> message "a lower cased message..."
{tell,<0.64.0>,"a lower cased message..."}
<0.64.0>: from <0.69.0> received message "a lower cased message..."
6> Pid1 ! {tell, Pid1, "Sending myself a message!"}.
<0.64.0>: sending <0.64.0> message "Sending myself a message!"
{tell,<0.64.0>,"Sending myself a message!"}
<0.64.0>: from <0.64.0> received message "Sending myself a message!"
7> Pid1 ! {message, "A direct message from the shell", self()}.
<0.64.0>: from <0.67.0> received message "A direct message from the shell"
{message,"A direct message from the shell",<0.67.0>}

A standalone example 一个独立的例子

Now consider this escript of a ping-pong service. 现在考虑一下乒乓球服务的手稿。 Notice there is only one kind of talker defined inside and it knows how to deal with target , ping and pong messages. 注意,这里只是一种内部定义健谈的,它知道如何处理targetpingpong消息。

#! /usr/bin/env escript

-mode(compile).

main([CountString]) ->
    Count = list_to_integer(CountString),
    ok = io:format("~p: Starting pingpong script. Will iterate ~p times.~n", [self(), Count]),
    P1 = spawn_link(fun talker/0),
    P2 = spawn_link(fun talker/0),
    pingpong(Count, P1, P2).


pingpong(Count, P1, P2) when Count > 0 ->
    P1 ! {target, P2},
    P2 ! {target, P1},
    pingpong(Count - 1, P1, P2);
pingpong(_, P1, P2) ->
    _ = erlang:send_after(1000, P1, {exit, self()}),
    _ = erlang:send_after(1000, P2, {exit, self()}),
    wait_for_exit([P1, P2]).


wait_for_exit([]) ->
    ok = io:format("~p: All done, Returing.~n", [self()]),
    halt(0);
wait_for_exit(Pids) ->
    receive
        {exiting, Pid} ->
            ok = io:format("~p: ~p is done.~n", [self(), Pid]),
            NewPids = lists:delete(Pid, Pids),
            wait_for_exit(NewPids)
    end.


talker() ->
    receive
        {target, Pid} ->
            ok = io:format("~p: Sending ping to ~p~n", [self(), Pid]),
            Pid ! {ping, self()},
            talker();
        {ping, From} ->
            ok = io:format("~p: Received ping from ~p. Replying with pong.~n", [self(), From]),
            From ! pong,
            talker();
        pong ->
            ok = io:format("~p: Received pong.~n", [self()]),
            talker();
        {exit, From} ->
            ok = io:format("~p: Received exit message from ~p. Retiring.~n", [self(), From]),
            From ! {exiting, self()}
    end.

There are some details there, like use of erlang:send_after/3 that are used because message sending is so fast that it will beat the speed of the calls to io:format/2 that slow down the actual talker processes and result in a weird situation where the exit messages (usually) arrive before the pings and pongs between the two talkers. 那里有一些细节,例如使用erlang:send_after/3 ,是因为消息发送是如此之快 ,以至于超过了对io:format/2的调用速度,从而减慢了实际的talker进程并导致了奇怪的情况。退出消息(通常)在两个讲话者之间的乒乓球和乒乓球之前到达的情况。

Here is what happens when it is run: 运行时会发生以下情况:

ceverett@changa:~/Code/erlang$ ./pingpong 2
<0.5.0>: Starting pingpong script. Will iterate 2 times.
<0.61.0>: Sending ping to <0.62.0>
<0.62.0>: Sending ping to <0.61.0>
<0.61.0>: Sending ping to <0.62.0>
<0.62.0>: Sending ping to <0.61.0>
<0.61.0>: Received ping from <0.62.0>. Replying with pong.
<0.62.0>: Received ping from <0.61.0>. Replying with pong.
<0.61.0>: Received ping from <0.62.0>. Replying with pong.
<0.62.0>: Received ping from <0.61.0>. Replying with pong.
<0.61.0>: Received pong.
<0.62.0>: Received pong.
<0.61.0>: Received pong.
<0.62.0>: Received pong.
<0.61.0>: Received exit message from <0.5.0>. Retiring.
<0.62.0>: Received exit message from <0.5.0>. Retiring.
<0.5.0>: <0.61.0> is done.
<0.5.0>: <0.62.0> is done.
<0.5.0>: All done, Returing.

If you run it a few times (or on a busy runtime) there is a chance that some of the output will be in different order. 如果您运行几次(或在繁忙的运行时),则某些输出可能会以不同的顺序运行。 That is just the nature of concurrency. 这仅仅是并发的本质。

If you are new to Erlang the above code might take a while to sink in. Play with that pingpong script yourself. 如果您是Erlang的新手,那么上面的代码可能要花点时间才能理解。您可以自己尝试使用该乒乓脚本。 Edit it. 编辑它。 Make it do new things. 让它做新的事情。 Create a triangle of pinging processes. 创建一个三角形的ping过程。 Spawn a random circuit of talkers that do weird things. 产生一个随机的讲话者行事,这些讲话者做奇怪的事情。 This will make sense suddenly once you mess around with it. 一旦您弄乱了它,这将突然变得有意义。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM