如何在Erlang中维护状态?

HIR*_*KUR 5 erlang

我见过人们在我读过的许多博客中使用dict,ordict,record维护状态.我发现它是非常重要的概念.

一般来说,我理解保持状态,递归的含义,但是当涉及到erlang时......我对它是如何处理有点模糊.

有帮助吗?

tko*_*wal 6

维护状态的最简单方法是使用gen_server行为.您可以阅读更多关于学习一些Erlang文档的内容.

gen_server 是过程,可以是:

  • 用给定状态初始化,
  • 可以定义同步和异步回调(同步用于以"请求 - 响应样式"查询数据,异步用于以"冒烟"方式更改状态)

它还有几个不错的OTP机制:

  • 它可以受到监督
  • 它为您提供基本的日志记录
  • 它的代码可以在服务器运行时升级而不会丢失状态
  • 等等...

概念上gen_server是一个无限循环,看起来像这样:

loop(State) ->
    NewState = handle_requests(State),
    loop(NewState).
Run Code Online (Sandbox Code Playgroud)

处理请求接收消息的位置.这样所有请求都被序列化,因此没有竞争条件.当然,给你所有的好东西,我描述的更复杂一点.

您可以选择要用于的数据结构State.通常使用记录,因为它们具有命名字段,但是因为Erlang 17映射可以派上用场.这个取决于你想要存储的内容.


zxq*_*xq9 6

状态是数据的当前排列。有时很难记住这一点,原因有二:

  • 状态既指程序中的数据,也指程序当前的执行点和“模式”。
  • 我们不必要地将其打造为某种神奇的东西。

考虑一下:

“进程的状态是什么?” 询问变量的现值。

“进程处于什么状态?” 通常指模式、选项、标志或当前执行位置。

如果你是图灵机,那么这些都是同样的问题;我们将这些想法分开,以便为我们提供方便的抽象来构建(就像编程中的其他所有内容一样)。

让我们考虑一下状态变量......

在许多较旧的语言中,您可以从您喜欢的任何上下文中更改状态变量,无论状态的修改是否适当,因为您可以直接管理它。在更现代的语言中,通过对变量强加类型声明、范围规则和公共/私有上下文,这会受到更多限制。这实际上是一场规则军备竞赛,每种语言都在寻找更多方法来限制允许赋值的时间。如果调度是并发编程中的挫败王子,那么赋值就是魔鬼本身。因此建造了各种笼子来管理他。

Erlang 通过设置基本规则来限制以不同方式允许赋值的情况,即每个函数的条目只能赋值一次,并且函数本身是过程范围的唯一定义,并且所有状态都纯粹由执行过程封装。(想想作用域的声明就可以理解为什么很多人觉得 Erlang 宏是一件坏事。)

这些分配规则(状态变量的使用)鼓励您将状态视为离散的时间片段。函数的每个条目都从干净的状态开始,无论函数是否递归。这与大多数其他语言中随时随地进行的就地修改的持续混乱情况有着根本的不同。在 Erlang 中,你永远不会问“现在X 的值是多少?” 因为它只能是在当前函数当前运行的上下文中最初指定的内容。这极大地限制了功能和进程内状态变化的混乱。

这些状态变量的详细信息以及它们的分配方式对于 Erlang 来说是附带的。您已经了解列表、元组、ETS、DETS、mnesia、数据库连接等。无论如何。了解 Erlang 风格的核心思想是如何管理分配,而不是这种或那种特定数据类型的附带细节。

“模式”和执行状态怎么样?

如果我们写这样的东西:

has_cheeseburger(BurgerName) ->
  receive
    {From, ask, burger_name} ->
        From ! {ok, BurgerName},
        has_cheeseburger(BurgerName);
    {From, new_burger, _SomeBurger} ->
        From ! {error, already_have_a_burger},
        has_cheeseburger(BurgerName);
    {From, eat_burger} ->
        From ! {ok, {ate, BurgerName}},
        lacks_cheeseburger()
  end.

lacks_cheeseburger() ->
  receive
    {From, ask, burger_name} ->
        From ! {error, no_burger},
        lacks_cheeseburger();
    {From, new_burger, BurgerName} ->
        From ! {ok, thanks},
        has_cheeseburger(BurgerName);
    {From, eat_burger} ->
        From ! {error, no_burger},
        lacks_cheeseburger()
  end.
Run Code Online (Sandbox Code Playgroud)

我们在看什么?一个循环。从概念上讲,它只是一个循环。通常,程序员会选择在代码中只编写一个循环,并向IsHoldingBurger循环添加一个类似的参数,并在子句中的每条消息之后检查它,receive以确定要采取的操作。

不过,上面两种操作模式的想法更加明确(它融入到结构中,而不是任意的程序测试)并且不那么冗长。我们通过编写基本上相同的循环两次来分离执行上下文,一次针对我们可能处于的每个条件,要么有汉堡,要么缺少汉堡。这是 Erlang 如何处理“有限状态机”概念的核心,它非常有用。OTP 在 gen_fsm 模块中包含一个围绕此想法构建的工具。您可以像我上面那样手动编写自己的 FSM,也可以使用 gen_fsm——无论哪种方式,当您发现自己遇到这样的情况时,以这种风格编写代码都会使推理变得更加容易。(对于除了最琐碎的 FSM 以外的任何事情,您都会非常欣赏 gen_fsm。)

结论

这就是 Erlang 中的状态处理。每个进程内的单赋值和绝对数据封装的基本规则使得不受控制的赋值的混乱变得无能为力(顺便说一句,这意味着您不应该编写巨大的进程)。一组有限的操作模式的极其有用的概念是由 OTP 模块 gen_fsm 抽象出来的,或者可以很容易地手工编写。

由于 Erlang 在限制单个进程内的状态混乱方面做得很好,并且使进程之间并发调度的噩梦完全不可见,因此只留下一个复杂性怪物:松散耦合的参与者之间交互的混乱。在爱兰格的心目中,这就是复杂性所在。困难的东西通常应该在消息的无人区中最终显现出来,而不是在功能或流程本身中。您的函数应该很小,对过程检查的需求相对较少(与 C 或 Python 相比),对模式标志和开关的需求几乎不存在。

编辑

以超级有限的方式重申帕斯卡的答案:

loop(State) ->
  receive
    {async, Message} ->
        NewState = do_something_with(Message),
        loop(NewState);
    {sync, From, Message} ->
        NewState = do_something_with(Message),
        Response = process_some_response_on(NewState),
        From ! {ok, Response},
        loop(NewState);
    shutdown ->
        exit(shutdown);
    Any ->
        io:format("~p: Received: ~tp~n", [self(), Any]),
        loop(State)
  end.
Run Code Online (Sandbox Code Playgroud)

重新阅读 tkowal 的回复以获得简单的版本。重新阅读 Pascal 的内容,以扩展相同的想法以包括服务消息。重新阅读上面的内容,了解相同的状态处理模式的稍微不同的风格,并添加输出意外消息。最后,重新阅读我上面写的两种状态循环,您会发现它实际上只是同一想法的另一个扩展。

请记住,您无法在函数的同一迭代中重新分配变量,但下一次调用可以具有不同的状态。这就是Erlang 中状态处理的范围。

这些都是同一事物的变体。我认为你期望有更多的东西,更广泛的机制或其他东西。那没有。限制赋值消除了您可能习惯在其他语言中看到的所有内容。在 Python 中你可以这样做somelist.append(NewElement),并且你现在的列表已经改变了。在 Erlang 中NewList = lists:append(NewElement, SomeList),SomeList 仍然与以前完全相同,并且返回了一个包含新元素的新列表。这是否真的涉及在后台复制不是你的问题。你处理这些细节,所以不要考虑它们。这就是 Erlang 的设计方式,这样就留下了单一的赋值并进行新的函数调用,以进入一个新的时间段,在这个时间段中,石板再次被擦干净。