【问题标题】:how to tackle a race condition between function calls如何解决函数调用之间的竞争条件
【发布时间】:2018-01-19 15:53:19
【问题描述】:

我已经使用 erlang 的消息传递构造构建了一个多人游戏(准确地说是 4 个玩家)。我以以下链接中的tictactoe游戏为例,但真正相似的是游戏中显示的消息传递构造:link

然后我选择在 ejabberd 多用户聊天室上运行这个游戏,我确实为此编写了一个 ejabberd 钩子。 但是如果你查看上面链接中文件tictactoe.erl中的NewGameState,你会发现没有办法找回它 出一个变量。

所以我使用了 mnesia 并将每个生成的新游戏状态写入这个 mnesia 表。 现在在我的 ejabberd 钩子中,我调用了我的游戏函数(即在每次调用时都会执行一系列模块->“gen_server,game_modules,mnesia_modules”) 在游戏函数调用下方的钩子中,我从 mnesia 表中读取游戏状态如下(这里的函数 myMessage 是 ejabberd 钩子中的函数):

myMessage({#message = Msg, C2SState})->
    some_other_module:game_func(Args),
    State=mnesia_module:read(key),

    {Msg, C2SState};
myMessage(Acc) ->
    Acc.

现在我的问题是当执行顺序为时读取操作给了我一个空表

some_other_module:game_func(Args),
 GameState=mnesia_module:read(key),

当我在这两行之间插入一个延迟为timer:sleep/1 时,如下所示(值 200 在经过一些不同值的试验后随机选择):

some_other_module:game_func(Args),
timer:sleep(200)
 GameState=mnesia_module:read(key),

我得到了正确的 GameState 值,因此建议我进行在线读取操作

GameState=mnesia_module:read(key),

在行some_other_module:game_func(Args)(这是一系列模块->“gen_server,game_modules,mnesia_modules”)能够执行mnesia模块并将GameState写入mnesia表之前执行/执行。

我不想使用timer:sleep/1,因为它不是可靠的解决方案,我该如何解决这个问题。

任何人都可以建议我在这里解决问题。我的意思是,任何人都可以建议我一种方法来通过除 mnesia 之外的任何其他方式来检索钩子内的 GameState,这样我就根本没有竞争条件。

或者ejabberd有什么方法可以提供一些我可以在这里使用的功能?

提前致谢。

【问题讨论】:

  • 你在使用 mnesia:dirty_* 函数吗?
  • no @Pouriya 我在 some_other_module:game_func(Args) 运行时使用 mnesia:write/1 写入数据,当 GameState=mnesia_module:read(键)被调用。
  • 你能显示代码吗?
  • 大部分代码与链接中给出的代码在同一行。不同的是,在链接中,在 tictactoe.erl 文件中,您会找到一个NewgameState,之后我调用了 mnesia :write/1 并将其与当前玩家 id 作为键存储。

标签: erlang ejabberd erlang-shell ejabberd-module ejabberd-hooks


【解决方案1】:

分布式节点之间的数据实时一致性是一个难题,您需要根据自己的需要定制解决方案。你没有说你在 mnesia 中使用什么样的交易,所以也许这会解决你的问题。

但是,这里有一个简单的解决方案可以帮助您思考问题:

首先,让我们调用您的一个节点master。在主节点上,启动一个处理游戏状态的 gen_server。现在,任何想要读取或写入游戏状态的人都需要将rpc:call/4 到主节点(除非他们已经在那里)变成gen_server:call/2。现在所有与游戏状态的交互都是同步的。

如果您每秒更新游戏状态的次数不超过几次,那么此解决方案应该非常适合您。如果游戏是独立的,那么每个游戏都是不同的 gen_server。

【讨论】:

    【解决方案2】:

    我正在尝试提供适合我的解决方案。希望它可以帮助某人。

    这是我所做的:

    首先我从图片中删除了失忆症。

    我首先在 start/2 函数中创建基本模块的 Pid(您可以想到问题中提供的链接上的 tictactoe.erl),然后我创建了一个 get_gs/0 函数在该模块中仅检索 GameState 如下(server 是我用来注册 Pid 的别名):

    get_gs()->
        server ! {get_gs, self()},
         receive
            GameState ->
                GameState
        end.
    

    然后在 loop() 函数里面我有:

    { get_gs, From } ->
               From ! GameState,
    
               loop(FirstPlayer, SecondPlayer, CurrentPlayer, GameState)
    

    然后创建一个实现gen_server架构的模块并按以下顺序调用一个函数(其中->表示函数调用,如A->B表示From A i call B):

    My custom hook on ejabberd->gen_server based module->gameclient:get_gs/0->gameserver:get_gs/0->tictactoe:get_gs/0
    

    我得到了当前的 GameState。

    感谢@Nathaniel Waisbrot 提出的宝贵建议。

    【讨论】:

    • 看起来不错!当您准备好接受更多复杂性时,我强烈建议您使用 OTP 构造(supervisor、gen_server、gen_statem)。了解它们需要一段时间,但它们是非常强大的工具,你会不断地使用。 (相比之下,Mnesia 学习起来也很复杂,但仅在特定情况下才有用。)
    • 是的,我肯定会研究 OTP 构造。感谢您的帮助。干杯
    猜你喜欢
    • 2019-09-19
    • 1970-01-01
    • 2013-10-11
    • 2019-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-06
    • 2016-11-16
    相关资源
    最近更新 更多