【问题标题】:Why my interface for learnyousomeerlang trade_fsm is failing?为什么我的 learnyousomeerlang trade_fsm 界面失败了?
【发布时间】:2018-08-02 02:17:57
【问题描述】:

我正在使用 learnyousomeerlang site 慢慢学习 erlang 语言,目前我在“Rage Against The Finite-State Machines”一章,该章节构建并描述了 trade_fsm.erl 的工作原理。作为我学习过程的一部分,我决定为这个系统编写一个界面,您可以在其中通过输入控制台命令来控制交易双方。我认为我在写这篇文章方面做得不错,但是由于某种原因我无法理解,每当我尝试开始交易时,客户就会崩溃。事情是这样的:

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> 

这是我的代码:

-module(z3).
-compile(export_all).

-record(state, {player1,
                player2,
                p1items=[],
                p2items=[],
                p1state,
                p2state,
                p1name="Carl",
                p2name="FutureJim"}).


init(FirstName, SecondName) ->
    {ok, Pid1} = trade_fsm:start_link(FirstName),
    {ok, Pid2} = trade_fsm:start_link(SecondName),
    S = #state{p1name=FirstName, p2name=SecondName,
            player1=Pid1, player2=Pid2,
            p1state=idle, p2state=idle},
    register(?MODULE, spawn(?MODULE, loop, [S])).

display_pids() ->
    ?MODULE ! display_pids,
    done.

p1_propose_trade() ->
    ?MODULE ! {wanna_trade, p1},
    done.

p2_accept_trade() ->
    ?MODULE ! {accept_trade, p2},
    done.

loop(S=#state{}) ->
    receive
        display_pids ->
            io:format("First player pid: {~p}~nSecond player pid: {~p}.~n", [S#state.player1, S#state.player2]),
            loop(S);
        {wanna_trade, Player} ->
            case Player of
                p1 ->
                    trade_fsm:trade(S#state.player1, S#state.player2);
                p2 ->
                    trade_fsm:trade(S#state.player2, S#state.player1);
                _ ->
                    io:format("[Debug:] Invalid player.~n")
            end,
            loop(S);
        {accept_trade, Player} ->
            case Player of
                p1 ->
                    trade_fsm:accept_trade(S#state.player1);
                p2 ->
                    trade_fsm:accept_trade(S#state.player2);
                _ ->
                    io:format("[Debug:] Invalid player.~n")
            end,
            loop(S);
        _ ->
            io:format("[Debug:] Received invalid command.~n"),
            loop(S)
    end.

谁能告诉我为什么这段代码会失败以及应该如何实现?

【问题讨论】:

  • 您在崩溃时遇到的错误是什么?我可以看到您的控制台输出中没有崩溃。
  • @matov 这是问题的一部分,没有错误消息,但 Pids 死了,不能再被“撞”(Pid !Msg)

标签: concurrency interface erlang


【解决方案1】:

当您调用z3:p1_propose_trade(). 时,它会将消息{wanna_trade, p1} 发送到已注册的进程z3。

消息在循环函数中被解释,该函数调用trade_fsm:trade(S#state.player1, S#state.player2); 转换为gen_fsm:sync_send_event(S#state.player1, {negotiate, 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 的行为,可以解释幕后发生的事情(为什么超时结束时没有任何打印输出,为什么 shell 准备好执行新命令而它应该等待答案?):

  • 如果您手动调用函数 trade(OwnPid, OtherPid),您将看到它在 30 秒超时之前不会返回,然后您会收到一条错误消息。
  • 当它被z3:p1_propose_trade(). 调用时,30 秒后不显示错误消息,但注册的进程 z3 死亡。

[编辑]

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

{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”界面以避免阻塞问题

{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);

【讨论】:

  • 对不起,我没有早点检查这个,学习erlang是我的爱好,最近我没有时间。但是您的解决方案有效!所以基本上当 trade_fsm 使用 gen_fsm:sync_send_event 时,我不得不将该调用包装在一个新进程中,否则 z3 进程会卡住等待它永远无法收到的答案,谢谢。
  • 出于测试目的是的,但通常您会在一个应用程序中涉及多个进程,而等待答案的进程不应该是触发答案的进程。您的用例很特别,因为请求交易和接受交易都是从 shell 发出的。您还可以在同一个 VM 中使用多个 shell(Ctrl G, s -> 创建一个新的 shell,c 1 和 c 2 连接到第一个或第二个 shell)
猜你喜欢
  • 2015-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-14
相关资源
最近更新 更多