【问题标题】:Erlang with multiple client shells - spawned process hangs in 'io' module具有多个客户端外壳的 Erlang - 生成的进程挂在“io”模块中
【发布时间】:2018-03-04 14:45:45
【问题描述】:

通过 Joe 的书,卡在第 12 章练习 1。该练习要求编写一个函数 start(AnAtom,Fun),它将 AnAtom 注册为生成(有趣)。我决定尝试一些看似更简单的方法 - 采用本章完成的“area_server”模块,并修改它的 start/0 函数,如下所示:

start() ->
    Pid = spawn(ex1, loop, []),
    io:format("Spawned ~p~n",[Pid]),
    register(area, Pid).

所以代替执行任意 Fun 的进程,我正在注册“循环”,它是 area_server 模块中的一个函数,完成所有工作:

loop() ->
    receive
        {From, {rectangle, Width, Ht}} ->
            io:format("Computing for rectangle...~n"),
            From ! {self(), Width*Ht},
            loop();
        {From, {square, Side}} ->
            io:format("Computing for square...~n"),
            From ! {self(), Side*Side},
            loop();
        {From, Other} ->
            io:format("lolwut?~n"),
            From ! {self(), {error, Other}},
            loop()
    end.

它似乎工作得很好:

1> c("ex1.erl").
{ok,ex1}
2> ex1:start(). 
Spawned <0.68.0>
true
3> 
3> area ! {self(), hi}.
lolwut?
{<0.61.0>,hi}
4> flush().
Shell got {<0.68.0>,{error,hi}}
ok
5> area ! {self(), {square, 7}}.
Computing for square...
{<0.61.0>,{square,7}}
6> flush().
Shell got {<0.68.0>,49}
ok

当我尝试测试多个进程是否可以与注册的“服务器”通信时,事情变得很糟糕。 (CTRL-G, s, c 2)

我在一个新的 shell 中,与第一个 shell 一起运行 - 但是当我从这个新 shell 向我的“区域”注册进程发送消息时,会发生一些令人讨厌的事情 - 当查询 process_info(whereis(area)), process从此状态移动:

 {current_function,{ex1,loop,0}},
 {initial_call,{ex1,loop,0}},

到这个:

 {current_function,{io,execute_request,2}},
 {initial_call,{ex1,loop,0}},

当消息队列开始增长时,消息没有得到处理。挂在模块io中,呵呵! io操作被阻止了?显然,该过程从我的 ex1:loop/0 移动到 io:execute_request/2 (不管是什么)...是我的愚蠢打印导致问题吗?

【问题讨论】:

标签: erlang erlang-shell


【解决方案1】:

您的进程正在执行您期望的操作除了在什么时候处理谁可以控制 STDOUT。是的,这可能会导致 shell 出现奇怪的行为。

所以让我们尝试这样的事情没有任何暗示去STDOUT的IO命令,看看会发生什么。下面是一个 shell 会话,我在其中定义了一个循环来累积消息,直到我要求它向我发送它所累积的消息。我们可以从这个例子中看到(它不会挂断谁被允许与单个输出资源对话)进程的行为符合预期。

需要注意的一点是,您不需要多个 shell 来与多个进程通信或从多个进程通信

注意shell中flush/0的返回值——这是一个特殊的shell命令,将shell的邮箱转储到STDOUT。

Eshell V9.0  (abort with ^G)
1> Loop = 
1>   fun L(History) ->
1>     receive
1>       halt ->
1>         exit(normal);
1>       {Sender, history} ->
1>         Sender ! History,
1>         L([]);
1>       Message ->
1>         NewHistory = [Message | History],
1>         L(NewHistory)
1>     end
1>   end.
#Fun<erl_eval.30.87737649>
2> {Pid1, Ref1} = spawn_monitor(fun() -> Loop([]) end).
{<0.64.0>,#Ref<0.1663562856.2369257474.102541>}
3> {Pid2, Ref2} = spawn_monitor(fun() -> Loop([]) end).
{<0.66.0>,#Ref<0.1663562856.2369257474.102546>}
4> Pid1 ! "blah".
"blah"
5> Pid1 ! "blee".
"blee"
6> Pid1 ! {self(), history}.
{<0.61.0>,history}
7> flush().
Shell got ["blee","blah"]
ok
8> Pid1 ! "Message from shell 1". 
"Message from shell 1"
9> Pid2 ! "Message from shell 1".
"Message from shell 1"
10> 
User switch command
 --> s
 --> j
   1  {shell,start,[init]}
   2* {shell,start,[]}
 --> c 2
Eshell V9.0  (abort with ^G)
1> Shell1_Pid1 = pid(0,64,0).
<0.64.0>
2> Shell1_Pid2 = pid(0,66,0).
<0.66.0>
3> Shell1_Pid1 ! "Message from shell 2".
"Message from shell 2"
4> Shell1_Pid2 ! "Another message from shell 2".
"Another message from shell 2"
5> Shell1_Pid1 ! {self(), history}.
{<0.77.0>,history}
6> flush().
Shell got ["Message from shell 2","Message from shell 1"]
ok
7> 
User switch command
 --> c 1

11> Pid2 ! {self(), history}.
{<0.61.0>,history}
12> flush().
Shell got ["Another message from shell 2","Message from shell 1"]
ok

【讨论】:

  • 谢谢伙计;我现在要研究你的例子来理解你的观点;然而,我对这个 io 模块的事情感到有些惊讶。为什么会有人交出?如中所示,我已从 Shell 1 向服务器发送消息。服务器打印到 STDOUT。然后我从 Shell 2 发送了另一组,但是这次服务器没有打印 [到另一个 shell 的输出流?]。为什么?我与旧版没有冲突,我们相差几秒钟。有什么东西还在锁着吗?但为什么呢?
  • 好吧,我能够复制你的例子,比照了。事实上,一切都指向 io 模块是罪魁祸首;我觉得这种行为非常可疑。您使用 spawn_monitor 而不是简单的 spawn 是否有原因?我只是在学习 Erlang/OTP,并试图让事情尽可能简单 =)
  • @alexakarpov 在监视器上:拥有孤立的进程(缺乏明确的终止条件)是一件坏事,所以这部分是出于习惯。我曾考虑过展示终止如何将消息发送到监控进程,在这种情况下,它是 only shell 1,但我认为这个例子会太长。至于 STDOUT 处理,shell 它自己的进程,因此当您切换到 shell 2 时,您会生成一个新进程并移交 STDOUT 资源的控制权。 这确实不应该中断,但是:后台有一些 IO 资源处理业务尚未排序。
  • @alexakarpov 关于 STDOUT 的处理需要注意的一点——这是我遇到的唯一一种情况:在同一个终端的不同本地 shell 中跳来跳去。在实践中,这实际上从来都不是问题,这可能就是它识别缓慢且修复缓慢的原因。 这种行为也可能是故意的:想象一下在多会话终端仿真器出现之前的日子里同时连接到 3 个远程系统。您的屏幕可能很快就会模糊不清。
  • @alexakarpov 我看到了。不过,我仍然不太确定是错误还是功能。可能是一个错误导致该功能在 90% 的时间出现明显的预期行为——但实际上是一个错误。我没有一个小时来阅读相关的资源,而且它从来没有给我造成过实际问题(但是,缺少对等分布式源存储库群,对我来说是个问题,这就是为什么我这周要发布一个......)。 TBH,Elixir 世界(和语法)中的信号::噪声比使我通常避免使用它。
猜你喜欢
  • 2017-01-13
  • 1970-01-01
  • 2014-06-26
  • 1970-01-01
  • 2012-04-03
  • 2022-01-20
  • 2015-06-03
  • 2021-08-12
  • 2017-12-31
相关资源
最近更新 更多