Concurrency 为什么我的LearnyYouSomerlang trade_fsm接口失败?

Concurrency 为什么我的LearnyYouSomerlang trade_fsm接口失败?,concurrency,interface,erlang,Concurrency,Interface,Erlang,我正在慢慢学习使用erlang语言,目前正在“愤怒反对有限状态机”一章中,该章构建并描述了如何工作。作为我学习过程的一部分,我决定为这个系统编写一个界面,在这里你可以通过键入控制台命令来控制交易双方。我认为我在写这篇文章方面做得不错,但是由于某种原因我无法理解,每当我试图开始交易时,客户就会崩溃。事情是这样的: 5> z3:init("a", "b"). true 6> z3:display_pids(). First player pid: {<0.64.0>} Sec

我正在慢慢学习使用erlang语言,目前正在“愤怒反对有限状态机”一章中,该章构建并描述了如何工作。作为我学习过程的一部分,我决定为这个系统编写一个界面,在这里你可以通过键入控制台命令来控制交易双方。我认为我在写这篇文章方面做得不错,但是由于某种原因我无法理解,每当我试图开始交易时,客户就会崩溃。事情是这样的:

5> z3:init("a", "b").
true
6> z3:display_pids().
First player pid: {<0.64.0>}
Second player pid: {<0.65.0>}.
done
7> z3:p1_propose_trade().
{a}: asking user <0.65.0> for a trade

{b}: <0.64.0> asked for a trade negotiation

done
8> z3:display_pids().    
done
9> 

有人能告诉我这个代码失败的原因以及应该如何实现吗?

当您调用
z3:p1\u propose\u trade()。
它将消息
{want\u trade,p1}
发送到注册的进程z3

消息在循环函数中解释,该函数调用
trade_fsm:trade(S#state.player1,S#state.player2)转换为
gen_fsm:sync#u send_事件(S#state.player1,{协商,S#state.player2},30000)。
。此呼叫是一个同步呼叫,正在等待fsm的答复,如果没有收到任何答复,则会在30秒后超时

在等待状态下,您已捕获语句中的消息:

idle({negotiate, OtherPid}, From, S=#state{}) ->
    ask_negotiate(OtherPid, self()),
    notice(S, "asking user ~p for a trade", [OtherPid]),
    Ref = monitor(process, OtherPid),
    {next_state, idle_wait, S#state{other=OtherPid, monitor=Ref, from=From}};
不向调用者返回应答值。你应该在最后一行用

    {reply, Reply, idle_wait, S#state{other=OtherPid, monitor=Ref, from=From}};
或者直接打电话给gen_fsm:reply/2

我没有在代码中挖掘太多,但是如果您将其更改为:

idle({negotiate, OtherPid}, From, S=#state{}) ->
    Reply = ask_negotiate(OtherPid, self()),
    notice(S, "asking user ~p for a trade", [OtherPid]),
    Ref = monitor(process, OtherPid),
    {reply, Reply, idle_wait, S#state{other=OtherPid, monitor=Ref, from=From}};
它没有停止,似乎工作正常

也许有人完全了解gen_fsm的行为,可以解释幕后发生了什么(为什么超时结束时没有打印输出,为什么外壳在等待回答时准备好执行新命令?)

  • 如果手动调用函数
    trade(OwnPid,OtherPid)
    ,您将看到它在达到30秒超时之前不会返回,然后您会收到一条错误消息
  • 当它被
    z3:p1\u propose\u trade().
    调用时,30秒后不会显示错误消息,但注册的进程z3将终止
[编辑]

我已经检查了代码应该如何工作,事实上,似乎没有必要修改fsm代码。当第二个用户接受协商时,回复应该来自流程2。因此,您不能以这种方式进行测试(循环正在等待答案,它无法发送accept_交易)。以下是一个有效的会话:

{ok,P1} = trade_fsm:start("a1").
{ok,P2} = trade_fsm:start("a2").
T = fun() -> io:format("~p~n",[trade_fsm:trade(P1,P2)]) end.
A = fun() -> io:format("~p~n",[trade_fsm:accept_trade(P2)]) end.
spawn(T). % use another process to avoid the shell to be locked
A(). 
您可以更改“想要交易”界面以避免阻塞问题

{wanna_trade, Player} ->
    case Player of
        p1 ->
            spawn(fun() -> trade_fsm:trade(S#state.player1, S#state.player2) end);
        p2 ->
            spawn(fun() -> trade_fsm:trade(S#state.player2, S#state.player1) end);
        _ ->
            io:format("[Debug:] Invalid player.~n")
    end,
    loop(S);

你在坠机事件中犯了什么错误?我可以看到您的控制台输出中没有崩溃。@matov这是问题的一部分,没有错误消息,但是Pid会死掉并且不能“砰砰”地响(Pid!Msg)。很抱歉,我没有早点检查这个问题,学习erlang是我的一个爱好,最近我没有太多时间。但是你的解决方案有效!所以基本上,当trade_fsm使用
gen_fsm:sync_send_event
时,我必须将该调用打包到一个新的进程中,否则z3进程将在等待它永远无法收到的答案时陷入困境,谢谢。出于测试目的,是的,但通常一个应用程序中会涉及多个进程,等待答案的人不应该是触发答案的人。您的用例是特别的,因为ask-for-trade和accept-to-trade都是从shell发出的。您还可以在同一个VM中使用多个shell(Ctrl-G,s->创建一个新shell,c 1和c 2连接到第一个或第二个shell)
{wanna_trade, Player} ->
    case Player of
        p1 ->
            spawn(fun() -> trade_fsm:trade(S#state.player1, S#state.player2) end);
        p2 ->
            spawn(fun() -> trade_fsm:trade(S#state.player2, S#state.player1) end);
        _ ->
            io:format("[Debug:] Invalid player.~n")
    end,
    loop(S);