带有多个客户端 shell 的 Erlang - 生成的进程挂在 'io' 模块中

Erlang with multiple client shells - spawned process hangs in 'io' module

阅读 Joe 的书,卡在第 12 章练习 1 上。该练习要求一个人编写一个函数 start(AnAtom,Fun) 来注册 AnAtom as spawn(Fun)。我决定尝试一些看似更简单的方法 - 使用本章完成的 'area_server' 模块,并修改它的 start/0 函数,如下所示:

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

所以代替执行任意 Fun 的进程,我正在注册 'loop',它是 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

当我尝试测试多个进程可以与已注册的 "server" 通信时,事情变糟了。 (CTRL-G, s, c 2)

我在第一个新 shell、运行 中 - 但当我从这个新 shell 向我的 'area' 注册进程发送消息时,一些令人讨厌的事情发生了 - 当查询 process_info(whereis(area)) 时,进程从这个状态移动:

 {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 (无论那是什么)...是我愚蠢的打印导致了问题吗?

除了处理谁在什么时候控制 STDOUT 之外,您的进程正在做您期望的事情 。是的,这可能会导致 shell.

中出现奇怪的行为

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

需要注意的一件事是 您不需要多个 shell 来与多个进程对话或从多个进程对话

注意 shell 中 flush/0 的 return 值——这是一个特殊的 shell 命令,将 shell 的邮箱转储到标准输出。

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